为什么将具有不同参数类型的函数存储到带有void *参数UB的函数指针中?

时间:2019-06-06 14:56:56

标签: c function-pointers

我最近偶然发现了一个有趣的问题(至少我认为是)。 一个小例子:

示例

#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,但不完全是原因。也许与如何在内部取消引用函数指针有关?

3 个答案:

答案 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

  

要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针。

类型Avoid不兼容。

答案 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;
}