为什么我不能将函数指针强制转换为(void *)?

我有一个函数,它接受一个字符串,一个字符串数组和一个指针数组,并在字符串数组中查找字符串,并从指针数组返回相应的指针。 因为我将它用于几个不同的东西,指针数组被声明为(void *)的数组,并且调用者应该知道实际存在哪种指针(因此它返回的返回值是什么类型的指针) )。

但是当我传入一个函数指针数组时,我在使用-Wpedantic编译时会收到警告:

铛:

 test.c:40:8: warning: assigning to 'voidfunc' (aka 'void (*)(void)') from 'void *' converts between void pointer and function pointer [-Wpedantic] 

GCC:

 test.c:40:8: warning: ISO C forbids assignment between function pointer and 'void *' [-Wpedantic] fptr = find_ptr("quux", name_list, (void **)ptr_list, 

这是一个测试文件,尽管警告确实正确打印“quux”:

 #include  #include  void foo(void) { puts("foo"); } void bar(void) { puts("bar"); } void quux(void) { puts("quux"); } typedef void (* voidfunc)(void); voidfunc ptr_list[] = {foo, bar, quux}; char *name_list[] = {"foo", "bar", "quux"}; void *find_ptr(char *name, char *names[], void *ptrs[], int length) { int i; for (i = 0; i < length; i++) { if (strcmp(name, names[i]) == 0) { return ptrs[i]; } } return NULL; } int main() { voidfunc fptr; fptr = find_ptr("quux", name_list, (void **)ptr_list, sizeof(ptr_list) / sizeof(ptr_list[0])); fptr(); return 0; } 

有没有办法修复警告,除了不用-Wpedantic编译,或复制我的find_ptr函数,一次用于函数指针,一次用于非函数指针? 有没有更好的方法来实现我想要做的事情?

你无法修复警告。 事实上,在我看来,它应该是一个很难的错误,因为将函数指针强制转换为其他指针是违法的,因为今天有一些架构,这不仅违反了C标准,而是一个实际的错误,它将使代码不行。 编译器允许它,因为许多架构都可以使用它,即使这些程序在某些其他架构上会严重崩溃。 但它不仅仅是理论上的标准违规,它还会导致真正的错误。

例如,在ia64上,函数指针实际上是两个值(或者至少用于我上次查看),这两个值都是跨共享库或程序和共享库进行函数调用所必需的。 同样,将函数指针转换为函数指针的常见做法是将函数返回给指向函数的指针返回void,因为你知道你将忽略返回值,这在ia64上也是非法的,因为这会导致陷阱值泄漏到寄存器中稍后会在一些不相关的代码段中导致崩溃。

不要强制转换函数指针。 总是让他们匹配类型。 这不仅仅是标准的迂腐,它是一种重要的最佳实践。

一种解决方案是添加一个间接级别。 这有助于解决很多问题。 不存储指向函数的指针,而是存储指向存储指向函数的指针的struct的指针。

 typedef struct { void (*ptr)(void); } Func; Func vf = { voidfunc }; ptrlist[123] = &vf; 

等等

这在C标准中早已被破解,并且从未被修复过 – 没有通用指针类型可用于指向函数和指向数据的指针。

在C89标准之前,所有C编译器都允许在不同类型的指针之间进行转换,而char *通常用作指向任何数据类型或任何函数的通用指针。 C89添加了void * ,但是放入一个只有对象指针可以转换为void *的子句,而不用定义对象是什么。 POSIX标准通过强制void *和函数指针可以来回安全地转换来解决这个问题。 存在如此多的代码将函数指针转换为void *并期望它正常工作。 因此,几乎所有的C编译器仍然允许它,并且仍然生成正确的代码,因为任何编译器都不会被拒绝作为不可用的。

严格地说,如果你想在C中有一个generics指针,你需要定义一个可以包含void *void (*)()并集,并使用函数指针的显式强制转换为正确的函数指针类型在打电话之前。

通过一些修改,您可以避免指针对话:

 #include  #include  void foo(void) { puts("foo"); } void bar(void) { puts("bar"); } void quux(void) { puts("quux"); } typedef void (* voidfunc)(void); voidfunc ptr_list[] = {foo, bar, quux}; char *name_list[] = {"foo", "bar", "quux"}; voidfunc find_ptr(char *name, char *names[], voidfunc ptrs[], int length) { int i; for (i = 0; i < length; i++) { if (strcmp(name, names[i]) == 0) { return ptrs[i]; } } return NULL; } int main() { voidfunc fptr; fptr = find_ptr("quux", name_list, ptr_list, sizeof(ptr_list) / sizeof(ptr_list[0])); fptr(); return 0; } 

语言律师理由是“因为C标准没有明确允许它”。 C11 6.3.2.3p1 / p8

1.指向void的指针可以转换为指向任何对象类型的指针。 指向任何对象类型的指针可以转换为指向void的指针,然后再返回; 结果应该等于原始指针。

8.指向一种类型的函数的指针可以被转换为指向另一种类型的函数的指针并且再次返回; 结果应该等于原始指针。 如果转换的指针用于调用类型与引用类型不兼容的函数,则行为未定义。

请注意,函数不是 C术语中的对象,因此没有任何东西允许您将指向函数的指针转换为指向void的指针,因此行为未定义

void *可铸性void *是一个常见的扩展。 C11 J.5共同扩展7 :

J.5.7函数指针强制转换

1.指向对象或void的指针可以转换为指向函数的指针,允许将数据作为函数调用(6.5.4)。

2.可以将指向函数的指针强制转换为指向对象的指针或使其无效,从而允许检查或修改函数(例如,通过调试器)(6.5.4)。

这是必需的,例如POSIX – POSIX有一个返回void *的函数dlsym ,但实际上它返回一个指向函数的指针或指向一个对象的指针,这取决于已解析的符号的类型。


至于为什么会发生这种情况 – 如果实现可以就此达成一致,那么C标准中没有任何内容未定义或未指定。 然而,有些平台假设空指针和函数指针的宽度相同会使事情变得困难。 其中之一是8086 16位实模式。


然后用什么呢? 您仍然可以将任何函数指针转换为另一个函数指针,因此您可以在任何地方使用generics函数指针void (*)(void) 。 如果需要void * 函数指针,则必须使用struct或union或者指定void *指向函数指针,或者确保代码仅在实现J.5.7的平台上运行;)

某些来源也建议使用void (*)() ,但现在它似乎在最新的GCC中触发警告,因为它没有原型。

我正在回答这个老问题,因为似乎现有答案中缺少一种可能的解决方案。

编译器禁止转换的原因是sizeof(void(*)(void))可能与sizeof(void*) 。 我们可以使函数更通用,以便它可以处理任何大小的条目:

 void *find_item(char *name, char *names[], void *items, int item_size, int item_count) { int i; for (i = 0; i < item_count; i++) { if (strcmp(name, names[i]) == 0) { return (char*)items + i * item_size; } } return NULL; } int main() { voidfunc fptr; fptr = *(voidfunc*)find_item("quux", name_list, ptr_list, sizeof(ptr_list[0]), sizeof(ptr_list) / sizeof(ptr_list[0])); fptr(); return 0; } 

现在find_entry()函数根本不需要直接处理该项。 相反,它只返回一个指向数组的指针,调用者可以在解除引用之前将其转换为指向funcpointer的指针。

(上面的代码片段假定了原始问题的定义。您也可以在此处查看完整代码: 在线试用! )