简体   繁体   English

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

[英]Why is storing a function with different argument type to function pointer with void* argument UB?

I recently stumbled across an interesting question (at least i think it is). 我最近偶然发现了一个有趣的问题(至少我认为是这样)。 A little example: 一个小例子:

Example

#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");
}

I asked myself if the above shown is valid code but on compilation GCC threw a warning: warning: assignment from incompatible pointer type . 我问自己上面显示的是上面是有效的代码但是在编译时GCC发出警告: warning: assignment from incompatible pointer type I did some research and in one thread someone stated the above would be undefined behaviour now im curious why this is UB since void* can be casted savely to any possible other type. 我做了一些研究,并且在一个线程中有人说上面的内容将是未定义的行为,现在很好奇为什么这是UB,因为void*可以被保存到任何可能的其他类型。 Is it just the standard saying "Nope thats undefined" or is there some explainable reason? 它只是标准的说法“没有未定义”或者是否有一些可解释的原因? All questions on StackOverflow I found state its UB but not exactly why. 关于StackOverflow的所有问题我发现它的UB但不完全是为什么。 Maybe it has something to do how a function pointer is dereferenced internally? 也许它有关于如何在内部取消引用函数指针的事情?

A void * can be safely converted to/from any other type, but that's not the conversion you're trying to do. void *可以安全地转换为/从任何其他类型转换,但这不是您尝试进行的转换。 You're trying to convert a int (*)(A *, A *) to a int (*)(void *, void*) . 您正在尝试将int (*)(A *, A *)转换为int (*)(void *, void*) Those are two very different things. 这是两件截然不同的事情。

The automatic conversion of void * does not apply to the arguments in a function pointer. void *的自动转换不适用于函数指针中的参数。 For two function pointers to be compatible, the number and type of the arguments must be compatible as well as the return type. 要使两个函数指针兼容,参数的数量和类型必须兼容,以及返回类型。

One of the reasons for this is that a void * need not have the same representation as other types of pointers. 其中一个原因是void *不需要与其他类型的指针具有相同的表示。 This is fine when simply converting to a void * and back which the standard explicitly allows, however it can be a problem when calling a function. 当简单地转换为标准明确允许的void *和back时,这很好,但是在调用函数时它可能是个问题。

Suppose a void * is represented with 8 bytes and a struct pointer is represented with 4 bytes. 假设void *用8个字节表示,struct指针用4个字节表示。 In your example, two 8 byte values would be pushed onto the stack but two 4 bytes values would be read from the stack as parameters in the function. 在您的示例中,两个8字节值将被压入堆栈,但是两个4字节值将作为函数中的参数从堆栈中读取。 This would result in invalid pointer values which would be subsequently dereferenced. 这将导致无效的指针值,随后将其解除引用。

6.7.5.3 p15 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. 相应的参数应具有兼容的类型。

The questions reduces recursively to whether A* is compatible with void* . 这些问题递归地减少了A*是否与void*兼容。

6.7.5.1 p2 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. 要使两个指针类型兼容,两者都应具有相同的限定条件,并且两者都应是兼容类型的指针。

The type A is not compatible with void . A型与void不兼容。

As dbush and alinsoar have pointed out, the problem is that int (*)(void *, void *) and int (*)(A *, A *) are not compatible. 正如dbush和alinsoar所指出的那样,问题是int (*)(void *, void *)int (*)(A *, A *)不兼容。 The way to fix this is to change the definition of comp as follows: 解决这个问题的方法是更改comp的定义,如下所示:

int comp( void *a, void *b )
{
  A *aa = a;
  A *bb = b;

  return aa->x - bb->x;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM