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

时间:2016-04-15 11:15:23

标签: c pointers function-pointers

I have a function that takes a string, an array of strings, and an array of pointers, and looks for the string in the array of strings, and returns the corresponding pointer from the array of pointers. Since I use this for several different things, the pointer array is declared as an array of (void *), and the caller should know what kind of pointers are actually there (and hence what kind of a pointer it gets back as the return value).

When I pass in an array of function pointers, however, I get a warning when I compile with -Wpedantic:

clang:

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,

Here's a test file, which despite the warning does correctly print "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;
}

Is there any way to fix the warning, other than not compiling with -Wpedantic, or duplicating my find_ptr function, once for function pointers and once for non-function pointers? Is there a better way to achieve what I'm trying to do?

7 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分:7)

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

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

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

答案 2 :(得分:6)

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

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

Func vf = { voidfunc };

ptrlist[123] = &vf;

答案 3 :(得分:2)

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

  

1。。指向void的指针可以转换成任何对象类型的指针,也可以转换成任何对象类型的指针。指向任何对象类型的指针都可以转换为   指向void并再次返回的指针;结果应等于   原始指针。

     

8。。指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应   比较等于原始指针。如果使用转换后的指针   调用其类型与引用的类型不兼容的函数   类型,行为是不确定的。

请注意,在C术语中,函数不是不是对象,因此没有任何东西可让您将指向函数的指针转换为指向void的指针,因此行为是 undefined

void *的可扩展性是常见的扩展。 C11 J.5 Common extensions 7

  

J.5.7函数指针强制转换

     

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

     

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

例如POSIX,这是必需的-POSIX具有返回dlsym的函数void *,但是实际上,根据类型,它返回返回指向函数的指针或指向对象的指针符号的解析。


为什么会这样-如果实现可以达成共识,则C标准中的任何内容都未定义或未指定。但是,在某些平台上,空指针和函数指针具有相同宽度的假设确实使事情变得困难。其中之一就是8086 16位实模式。


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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

正如其他答案所指出的那样,您不应被允许将函数指针分配给诸如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;

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

答案 6 :(得分:0)

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

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

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