我最近偶然发现了一个有趣的问题(至少我认为是)。 一个小例子:
#include <stdio.h>
typedef struct A {
int x;
} A;
int (*func)(void*, void*);
int comp(A* a, A* b) {
return a->x - b->x;
}
int main() {
func = comp;
A a;
A b;
a.x = 9;
b.x = 34;
printf("%d > %d ? %s\n", a.x, b.x, func(&a, &b) > 0 ? "true" : "false");
}
我问自己以上显示的代码是否有效,但是在编译时,GCC发出了警告:warning: assignment from incompatible pointer type
。我做了一些研究,现在在一个线程someone stated the above would be undefined behaviour中很好奇为什么使用UB,因为void*
可以轻松地转换为任何其他类型。只是标准的说法是“没有定义”,还是有一些可以解释的原因?我发现所有关于StackOverflow的问题都陈述了它的UB,但不完全是原因。也许与如何在内部取消引用函数指针有关?
答案 0 :(得分:5)
void *
可以安全地与任何其他类型进行转换,但这不是您要尝试的转换。您正在尝试将int (*)(A *, A *)
转换为int (*)(void *, void*)
。那是两件事。
void *
的自动转换不适用于函数指针中的参数。为了使两个函数指针兼容,参数的数量和类型以及返回类型必须兼容。
其原因之一是void *
不必具有与其他类型的指针相同的表示形式。仅当转换为void *
并由标准明确允许返回时,这很好,但是调用函数时可能会出现问题。
假设void *
用8个字节表示,而结构指针用4个字节表示。在您的示例中,将两个8字节的值压入堆栈,但将从堆栈中读取两个4字节的值作为函数中的参数。这将导致无效的指针值,该值随后将被取消引用。
答案 1 :(得分:2)
6.7.5.3 p15
要使两个函数类型兼容,两者都应指定兼容的返回类型。此外,参数类型列表(如果同时存在)应在参数数量和省略号终止符的使用上达成共识;相应的参数应具有兼容的类型。
问题递归地归结为A*
是否与void*
兼容。
6.7.5.1 p2
要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针。
类型A
与void
不兼容。
答案 2 :(得分:1)
正如dbush和alinsoar指出的那样,问题在于int (*)(void *, void *)
和int (*)(A *, A *)
不兼容。解决此问题的方法是如下更改comp
的定义:
int comp( void *a, void *b )
{
A *aa = a;
A *bb = b;
return aa->x - bb->x;
}