C:将 void (*)(void *) 转换为 void (*)(char *)

C: Cast void (*)(void *) to void (*)(char *)

如果我尝试使用参数 void (*)(char *) 和实际 void (*)(void *) 调用函数,我会收到此错误:

note: expected ‘void (*)(char *)’ but argument is of type ‘void (*)(void *)’

不是可以把void (*)(void *)转成void (*)(char *)吗?如果不是,为什么不呢? 如果是安全的,不抑制所有投射错误怎么可能抑制错误。

谢谢!

我同意 应该 是 'assignment compatibility' 当传入 void (*)(void *) 类型的实际参数时 void (*)(char *) 是预期的。

对某些人来说这可能看起来很奇怪,因为 void * 是较不具体的类型,所以您不应该被允许将它分配给类型 char * 的参数或变量。但在这种情况下,情况恰恰相反。这有时被描述为 'contravariance'。最好用一个例子来证明。

#include <stdio.h>
#include <string.h>

char globalData[10];

void PassHello(void (*func)(char *))
{
    char *text = "hello";
    func(text);
}

void CopyData(void *source)
{
    memcpy(globalData, source, sizeof globalData);
}

void CopyHello(void)
{
    PassHello(CopyData);
}   

int main()
{
    CopyHello();
    puts(globalData);
}

func(text)调用CopyData应该没有问题。即使 CopyData 被声明为接受 void * 作为参数,它也应该接受更具体的类型,在本例中为 char *.

这是违反直觉的,但却是事实:void (*)(void *) 作为一种类型 void (*)(char *) 更具体 就是逆变的全部内容。

不幸的是,C 语言不关心逆变,只允许对 naked void * 进行隐式指针转换。编译器可以将其实现为语言扩展,但当前的主要编译器不会。

因此,上面的代码给出了类型错误。

注: 正如其他张贴者指出的那样,C 标准不保证不同指针类型兼容这一事实证明了类型错误是合理的。 C 编译器可以根据指针的类型随意生成不同的代码。理论上也可以对 'nested' 类型(在这种情况下,函数指针的参数类型)进行这项工作,但编译器的复杂性可能会激增。所以在某处画一条线是有意义的; C 不是 Haskell.

我认为最干净的解决方案(即不涉及显式类型转换)是围绕 void (*)(void *) 函数指针创建一个 'wrapper' 函数。例如:

void CopyCharData(char *source)
{
    CopyData(source);
}

然后传递包装器:

PassHello(CopyCharData);

这将编译并且 运行 就好了。

现场演示: https://repl.it/repls/ImmediateWindingOrders

当您为声明为函数指针的函数参数传递实参时,C 标准中的约束要求该实参是指向兼容函数类型的指针(或空指针常量).要使函数类型兼容,它们声明的参数类型必须兼容。但是char *void *是不同的类型,互不兼容。

事实上,C标准要求一个char *和一个void *具有相同的表示,并允许它们轻松地来回转换,但它仍然指定它们不兼容.除其他事项外,这些类型规则有助于捕获错误——当程序员输入错误的名称或他们想要使用的表达式时,如果类型不匹配,编译器可能会捕获它。

因此,void (*)(char *)void (*)(void *) 不兼容,如果您尝试在需要另一个的地方使用一个,编译器会警告您。

您可以使用显式强制转换将其中一个指针转换为另一种类型。将参数传递给函数时,如果满足某些规则,C 标准的规则会将参数隐式转换为参数类型。但是,通过强制转换明确请求的转换规则更为广泛。您可以将任何函数指针转换为任何函数指针类型。因此,如果 xvoid (*)(void *),您可以将其转换为 (void (*)(char *)) x,并且您可以将其传递给声明为具有该类型的参数。

但是,您可以进行该转换这一事实并不能保证指针在用于调用函数时会起作用。允许转换函数指针的规则主要是为了允许将函数指针临时转换为另一种类型,以某种通用形式存储在传递中,然后再转换回其原始类型,然后再用于调用函数。允许转换的规则并没有说转换后的函数指针在调用参数不兼容的函数时会起作用。

两种函数指针类型不等价的原因可能是调用约定不同。 C 不假定 void *char * 参数以相同的方式传递(尽管在大多数处理器上它们这样做)。理论上,这些类型甚至可以有不同的编码。所以我们不能假设一个期望 char * 的函数和一个期望 void * 的函数共享相同的调用约定。这就是编译器抱怨的原因。
“你这个骗子!C 确实允许我将 char * 传递给需要 void * 的函数!我已经这样做多年了!”。
C保证所有指针都可以在void *中安全转换(注意:转换可能需要一些处理,原则上),它也适用从some *自动转换到 void * 传递参数时,前提是编译器知道参数的原始类型和函数的实际签名。如果函数是由指针调用的,编译器必须从携带函数指针的变量类型中获取函数的签名。在您的例子中,变量是 void (*)(char *),因此编译器将生成代码来传递 char *,而不是像实际函数所期望的那样先将其转换为 void *。这是未定义行为的情况。