为什么将具有不同参数类型的函数存储到具有 void* 参数 UB 的函数指针?
Why is storing a function with different argument type to function pointer with void* argument UB?
我最近偶然发现了一个有趣的问题(至少我认为是)。
一个小例子:
例子
#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
。我做了一些研究,在一个线程中 现在很好奇为什么这是 UB,因为 void*
可以安全地转换为任何可能的其他类型。这只是标准说法“不,那是未定义的”还是有一些可以解释的原因?我在 Whosebug 上发现的所有问题都说明了它的 UB,但没有确切说明原因。也许它与如何在内部取消引用函数指针有关?
A void *
可以安全地转换 to/from 任何其他类型,但这不是您要尝试进行的转换。您正在尝试将 int (*)(A *, A *)
转换为 int (*)(void *, void*)
。这是两个截然不同的东西。
void *
的自动转换不适用于函数指针中的参数。为了使两个函数指针兼容,参数的数量和类型以及 return 类型必须兼容。
其中一个原因是 void *
不需要与其他类型的指针具有相同的表示形式。当简单地转换为标准明确允许的 void *
和返回时这很好,但是在调用函数时可能会出现问题。
假设一个void *
用8个字节表示,一个结构指针用4个字节表示。在您的示例中,两个 8 字节的值将被压入堆栈,但两个 4 字节的值将从堆栈中读取作为函数中的参数。这将导致无效的指针值随后被取消引用。
6.7.5.3 p15
For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.
问题递归地减少到 A*
是否与 void*
兼容。
6.7.5.1 p2
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
类型 A
与 void
不兼容。
正如 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;
}
我最近偶然发现了一个有趣的问题(至少我认为是)。 一个小例子:
例子
#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
。我做了一些研究,在一个线程中 void*
可以安全地转换为任何可能的其他类型。这只是标准说法“不,那是未定义的”还是有一些可以解释的原因?我在 Whosebug 上发现的所有问题都说明了它的 UB,但没有确切说明原因。也许它与如何在内部取消引用函数指针有关?
A void *
可以安全地转换 to/from 任何其他类型,但这不是您要尝试进行的转换。您正在尝试将 int (*)(A *, A *)
转换为 int (*)(void *, void*)
。这是两个截然不同的东西。
void *
的自动转换不适用于函数指针中的参数。为了使两个函数指针兼容,参数的数量和类型以及 return 类型必须兼容。
其中一个原因是 void *
不需要与其他类型的指针具有相同的表示形式。当简单地转换为标准明确允许的 void *
和返回时这很好,但是在调用函数时可能会出现问题。
假设一个void *
用8个字节表示,一个结构指针用4个字节表示。在您的示例中,两个 8 字节的值将被压入堆栈,但两个 4 字节的值将从堆栈中读取作为函数中的参数。这将导致无效的指针值随后被取消引用。
6.7.5.3 p15
For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.
问题递归地减少到 A*
是否与 void*
兼容。
6.7.5.1 p2
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
类型 A
与 void
不兼容。
正如 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;
}