不带参数且 return 为 void 的函数指针类型是否可以与带参数且 return 值的函数一起使用?

Can a function pointer type that takes no arguments and returns void, be used with functions that do take arguments and return a value?

在GTK库中,可以找到如下定义:

/**
 * GCallback:
 * 
 * The type used for callback functions in structure definitions and function 
 * signatures. This doesn't mean that all callback functions must take no 
 * parameters and return void. The required signature of a callback function 
 * is determined by the context in which is used (e.g. the signal to which it 
 * is connected). Use G_CALLBACK() to cast the callback function to a #GCallback. 
 */
typedef void  (*GCallback)              (void);

这大概是函数指针的 typedef 不带参数并且 return void.

但是 - 在 GTK 站点的 Hello World 示例中,显示了以下代码:

g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

其中 activate 是一个函数 returns void 但有两个参数。 (G_CALLBACK 是一个简单转换为 GCallback 的宏)。

确实 - GCallback typedef 上方的评论表明:

This doesn't mean that all callback functions must take no parameters and return void

这段代码确实可以编译运行。这怎么可能?

C11 标准 §6.3 Conversions, and more precisely §6.3.2.3 Pointers ¶8 说:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

GTK 代码让程序员有责任将适当类型的函数指针传递给接受回调的函数。

g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

Where activate is a function that returns void but takes two parameters. (G_CALLBACK is a macro that simply casts to GCallback).

假设两个参数是int;他们的类型与讨论巧合。

extern void activate(int, int);

g_signal_connect() 中的代码获得 4 个指针。第三个是回调;它的正式类型为 void (*callback)(void).

g_signal_connect()里面的代码希望用2个整数(arg1arg2)调用回调,所以需要使用:

((void (*)(int, int)callback)(arg1, arg2);

强制 callback 的 'generic' 类型为正确的函数指针类型——否则,它无法避免调用未定义的行为。你需要知道 g_signal_connect() 需要这样一个指针作为回调参数,转换为泛型类型,你必须传递给它这样一个适当转换的指针。

还要记住,展示 'undefined behaviour' 的一种方式是 'behave as expected, even though the expectations are not guaranteed by the standard'。表现出未定义行为的其他方式包括崩溃或损坏内存。


旁注。

C11 §6.2.5 Types ¶28 说:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.48) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

48) The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

§6.3.2.3¶8的要求似乎暗示所有指向不同函数类型的指针必须具有相同的表示和对齐要求;否则,就很难保证 §6.3.2.3¶8.

的往返转换要求

§6.2.5¶28 的另一个后果是您无法在指向函数类型的指针和指向对象类型(例如 void * 的指针之间可靠地转换。这会对 dlsym() 等函数产生影响;很难干净地使用它们——如果你启用了严格的警告级别,编译器可能会抱怨。

编译一些在函数指针和对象指针之间转换的代码(反之亦然),GCC 9.3.0 gcc -std=c99 -O3 -Wall -pedantic -Wdeclaration-after-statement -Wold-style-definition -Wold-style-declaration -Wnested-externs -Wmissing-prototypes -Werror … 给出:

…: error: ISO C forbids conversion of function pointer to object pointer type [-Werror=pedantic]
…: error: ISO C forbids conversion of object pointer to function pointer type [-Werror=pedantic]

如果你没有-Werror-pedantic-errors,这是一个警告,如果你没有-pedantic-pedantic-errors,它会被忽略.

注意 -pedantic(又名 -Wpedantic)和 -pedantic-errors 之间的差异,如 GCC 在 Options to Request or Suppress Warnings.

下记录的那样