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

sag*_*ian 11 c pointers function-pointers

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

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

铛:

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

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,
Run Code Online (Sandbox Code Playgroud)

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

Art*_*Art 13

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

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

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

  • 很好的答案,但我有一个问题:将指向(任何)函数的指针存储在 `void (*f)()` 中并在调用时将其转换为正确的类型是否安全? (2认同)
  • @HolyBlackCat根据我对今天存在的架构的了解,是的.从标准的角度来看,没有.从未来兼容性的角度来看,我宁愿不这样做.从什么公司打破它的时候的角度来看,去吧.看看你可以逃脱的一个好方法是看大型项目.例如,如果Linux内核在编译器严格控制某些内容时中断,那么你可以非常肯定你会听到它并且至少会有标志来禁用它.硬件架构也是如此,很少有人会构建一个Linux无法运行的CPU. (2认同)
  • [这个答案](http://stackoverflow.com/a/5579907/149138)与你的相矛盾.特别是,它引用了标准,该标准指示函数指针可以转换为任何其他函数指针类型并再次返回. (2认同)
  • @technosaurus 不正确-o。在调用它之前,您必须始终向后转换为正确的函数指针类型,无论它看起来“多么接近”。 (2认同)

Ant*_*ala 10

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

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

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

请注意,函数不是C 术语中的对象,因此无法将指向函数的指针转换为指向 void 的指针,因此行为未定义

Castability tovoid *是一个常见的扩展。C11 J.5 通用扩展 7

J.5.7 函数指针强制转换

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

2.指向函数的指针可以转换为指向对象或void 的指针,允许检查或修改函数(例如,由调试器)(6.5.4)。

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


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


那用什么来代替呢?您仍然可以将任何函数指针转换为另一个函数指针,因此您可以void (*)(void)在任何地方使用通用函数指针。如果您同时需要函数指针void * 函数指针,则必须使用结构体或联合体或分配void *来指向函数指针,或确保您的代码仅在实现 J.5.7 的平台上运行;)

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


Chr*_*odd 7

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

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

严格地说,如果你想在C中有一个泛型指针,你需要定义一个可以包含a void *或a 的联合,void (*)()并在调用它之前使用函数指针的显式强制转换为正确的函数指针类型.

  • @pmor:6.3.2.3专门列出了所有合法的指针转换——函数指针和对象指针之间的转换没有列出,因此是未定义的。 (3认同)
  • “*这是 C 标准中早已被破坏且从未被修复的东西*”有点不准确。这是硬件标准化限制,而不是语言标准限制。 (2认同)

n. *_* m. 6

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

typedef struct
{
   void (*ptr)(void);
} Func;

Func vf = { voidfunc };

ptrlist[123] = &vf;
Run Code Online (Sandbox Code Playgroud)

等等

  • 你不需要`struct`; 你可以使用指针指针. (3认同)
  • @davmac是的,但是你可以在结构中添加更多东西并构建一个穷人的闭包. (3认同)