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

Why can't I cast a function pointer to (void *)?

我有一个函数,它接受一个字符串、一个字符串数组和一个指针数组,并在字符串数组中查找字符串,returns 从数组中查找相应的指针指针。因为我将它用于几个不同的事情,所以指针数组被声明为 (void *) 的数组,并且调用者应该知道实际存在什么样的指针(因此它返回什么样的指针作为 return 值).

然而,当我传入一个函数指针数组时,我在使用 -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 <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;
}

除了不使用 -Wpedantic 编译或复制我的 find_ptr 函数(一次用于函数指针,一次用于非函数指针)之外,还有什么方法可以修复警告吗?有没有更好的方法来实现我想要做的事情?

一个解决方案是添加一个间接级别。这对很多事情都有帮助。不是存储指向函数的指针,而是存储指向 struct 的指针,存储指向函数的指针。

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

Func vf = { voidfunc };

ptrlist[123] = &vf;

等等

您无法修复警告。事实上,在我看来,这应该是一个硬错误,因为将函数指针转换为其他指针是非法的,因为当今的体系结构中,这不仅违反了 C 标准,而且是一个实际错误,将使代码不行。编译器允许它,因为即使这些程序在其他一些体系结构上会严重崩溃,许多体系结构也会逃脱它。但这不仅仅是理论上的标准违规,它会导致真正的错误。

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

不要转换函数指针。始终让它们匹配类型。这不仅仅是标准的迂腐,而是重要的最佳实践。

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

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

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 标准中早已被打破并且从未被修复 -- 没有通用指针类型可用于函数指针和数据指针。

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

严格来说,如果你想在 C 中有一个通用指针,你需要定义一个可以容纳 void *void (*)() 的联合,并使用函数的显式转换在调用之前指向正确的函数指针类型。

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

编译器禁止转换的原因是 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() 函数根本不需要直接处理项目。相反,它只是 returns 一个指向数组的指针,调用者可以在取消引用之前将其转换为指向函数指针的指针。

(上面的代码片段采用了原始问题的定义。您也可以在此处查看完整代码:try it online!

语言律师原因是"because C standard does not explicitly allow it."C11 6.3.2.3p1/p8

1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

8. A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

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

Castability 到 void * 是一个常见的扩展。 C11 J.5 Common extensions 7:

J.5.7 Function pointer casts

1. A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).

2. A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).

例如 POSIX 需要 - POSIX 有一个函数 dlsym returns void * 但实际上 returns指向函数的指针或指向对象的指针,具体取决于解析的符号类型。


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


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

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

正如其他答案中所指出的,您不应被允许将函数指针分配给对象指针,例如 void*。但是您可以安全地将函数指针分配给任何函数指针。在 C++ 中使用 reinterpret_cast

举个例子:

typedef void(*pFun)(void);
double increase(double a){return a+1.0;}

pFun ptrToFunc = reinterpret_cast<void(*)(void)>(increase);

平淡无奇

pFun ptrToFunc = increase;

无法在多个编译器上编译。