简体   繁体   English

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

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

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).由于我将它用于几种不同的事情,指针数组被声明为 (void *) 数组,并且调用者应该知道实际存在什么样的指针(因此它返回什么样的指针作为返回值)。

When I pass in an array of function pointers, however, I get a warning when I compile with -Wpedantic :但是,当我传入函数指针数组时,使用-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":这是一个测试文件,尽管有警告,但它确实正确打印了“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?除了不使用-Wpedantic编译,或者复制我的 find_ptr 函数,一次用于函数指针,一次用于非函数指针,有什么方法可以修复警告? Is there a better way to achieve what I'm trying to do?有没有更好的方法来实现我想要做的事情?

You can't fix the warning.您无法修复警告。 In fact, in my opinion it should be a hard error since it's illegal to cast function pointers to other pointers because there are architectures out there today where this isn't just a violation of the C standard but an actual error that will make the code not work.事实上,在我看来,这应该是一个硬错误,因为将函数指针转换为其他指针是非法的,因为今天的架构不仅违反了 C 标准,而且是一个实际的错误,它会导致代码不工作。 Compilers allow it because many architectures get away with it even though those programs will crash badly on some other architectures.编译器允许它,因为即使这些程序会在其他一些架构上严重崩溃,许多架构也能逃脱。 But it's not just a theoretical standard violation, it's something that causes real bugs.但这不仅仅是理论上的标准违规,它会导致真正的错误。

For example on ia64 function pointers are (or at least used to be last time I looked) actually two values, both necessary to make function calls across shared libraries or a program and a shared library.例如,在 ia64 上,函数指针实际上是(或者至少我上次查看时曾经使用过)两个值,这两个值都是跨共享库或程序和共享库进行函数调用所必需的。 Likewise, the common practice to cast and call function pointers to functions returning a value to a pointer to a function returning void because you know you'll ignore the return value anyway is also illegal on ia64 because that can lead to trap values leaking into registers causing crashes in some unrelated piece of code many instructions later.同样,将函数指针强制转换并调用函数指针的常见做法返回一个指向返回 void 的函数的值,因为您知道无论如何都会忽略返回值在 ia64 上也是非法的,因为这可能导致陷阱值泄漏到寄存器中在许多指令之后导致一些不相关的代码段崩溃。

Don't cast function pointers.不要强制转换函数指针。 Always have them match types.始终让它们匹配类型。 This is not just standards pedantry, it's an important best practice.这不仅仅是标准的迂腐,它还是一个重要的最佳实践。

One solution is to add a level of indirection.一种解决方案是添加一个间接级别。 This helps with lots of things.这对很多事情都有帮助。 Instead of storing a pointer to a function, store a pointer to a struct storing a pointer to a function.不是存储指向函数的指针,而是存储指向struct的指针,该struct存储指向函数的指针。

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

Func vf = { voidfunc };

ptrlist[123] = &vf;

etc.等。

The language lawyering reason is "because C standard does not explicitly allow it."语言律师的理由是“因为 C 标准没有明确允许它”。 C11 6.3.2.3p1/p8 C11 6.3.2.3p1/p8

1. A pointer to void may be converted to or from a pointer to any object type. 1.指向 void 的指针可以与指向任何对象类型的指针相互转换。 A pointer to any object type may be converted to a pointer to void and back again;指向任何对象类型的指针都可以转换为指向 void 的指针,然后再返回; 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; 8.指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再返回; 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.如果使用转换后的指针调用类型与引用类型不兼容的函数,则行为未定义。

Notice that a function is not an object in C terminology, hence there is nothing that allows you to convert a pointer to a function to a pointer to void, hence the behaviour is undefined .请注意,函数不是C 术语中的对象,因此没有任何东西可以让您将指向函数的指针转换为指向 void 的指针,因此行为是undefined

Castability to void * is a common extension though.虽然可铸造到void *是一个常见的扩展。 C11 J.5 Common extensions 7 : C11 J.5 通用扩展 7

J.5.7 Function pointer casts J.5.7 函数指针强制转换

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). 1.指向对象或指向 void 的指针可以转换为指向函数的指针,从而允许将数据作为函数调用 (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). 2.指向函数的指针可以转换为指向对象的指针或指向 void 的指针,允许检查或修改函数(例如,由调试器)(6.5.4)。

This is required by for example POSIX - POSIX has a function dlsym that returns void * but in fact it returns either a pointer to a function or a pointer to an object, depending of the type of the symbol resolved.这是例如 POSIX 所要求的 - POSIX 有一个函数dlsym返回void *但实际上它返回一个指向函数的指针或一个指向对象的指针,这取决于解析的符号的类型。


As to why this happens - nothing in C standard is undefined or unspecified if the implementations could agree on it.至于为什么会发生这种情况 - 如果实现可以达成一致,C 标准中没有任何内容是未定义或未指定的。 However there were and are platforms where the assumption that a void pointer and function pointer would be of the same width would really make things difficult.然而,在某些平台上,假设 void 指针和函数指针具有相同的宽度,这确实会让事情变得困难。 One of these is the 8086 16-bit real mode.其中之一是 8086 16 位实模式。


And what to use instead then?那用什么来代替呢? You can still cast any function pointer to another function pointer, so you can use a generic function pointer void (*)(void) everywhere.您仍然可以将任何函数指针强制转换为另一个函数指针,因此您可以在任何地方使用通用函数指针void (*)(void) If you need both void * and a function pointer, you must use a struct or union or allocate void * to point to the function pointer, or ensure that your code only runs on platforms where J.5.7 is implemented ;)如果您同时需要void *函数指针,则必须使用结构体或联合体或分配void *以指向函数指针,或确保您的代码仅在实现 J.5.7 的平台上运行;)

void (*)() is recommended by some sources too, but right now it seems to trigger a warning in latest GCCs because it doesn't have a prototype. void (*)()也被一些来源推荐,但现在它似乎在最新的 GCC 中触发警告,因为它没有原型。

This is something that has long been broken in the C standard and has never been fixed -- there is no generic pointer type that can be used for pointers to functions and pointers to data.这在 C 标准中早已被打破并且从未得到修复——没有可用于指向函数的指针和指向数据的指针的通用指针类型。

Before the C89 standard, all C compilers allowed converting between pointers of different types, and char * was generally used as a generic pointer that might point to any data type or any function.在 C89 标准之前,所有 C 编译器都允许在不同类型的指针之间进行转换,而char *通常用作可能指向任何数据类型或任何函数的通用指针。 C89 added void * , but put in a clause that only object pointers could be converted to void * , without ever defining what an object is. C89 添加了void * ,但放入了一个子句,只有对象指针可以转换为void * ,而没有定义对象是什么。 The POSIX standard fixes this issue by mandating that void * and function pointers are safely convertable back and forth. POSIX 标准通过强制要求void *和函数指针可以安全地来回转换来解决这个问题。 So much code exists that converts function pointers to void * and expects it to work properly.存在大量将函数指针转换为void *并期望它正常工作的代码。 As a result, pretty much all C compilers still allow it, and still generate the correct code, as any compiler that did not would be rejected as unusable.结果,几乎所有的 C 编译器仍然允许它,并且仍然生成正确的代码,因为任何不这样做的编译器都会被拒绝为不可用。

Strictly speaking, if you want to have a generic pointer in C, you need to define a union that can hold either a void * or a void (*)() and use an explicit cast of the function pointer to the correct function pointer type before calling it.严格来说,如果你想在 C 中有一个泛型指针,你需要定义一个联合,它可以保存一个void *或一个void (*)()并使用函数指针的显式转换到正确的函数指针类型在调用它之前。

With some modification you can avoid pointer conversations:通过一些修改,您可以避免指针对话:

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

As pointed out in other answers you shouldn't be allowed to assign a function pointer to an object pointer such as a void*.正如其他答案中指出的那样,您不应被允许将函数指针分配给诸如 void* 之类的对象指针。 But you can safely assign a function pointer to any function pointer.但是您可以安全地将函数指针分配给任何函数指针。 Use reinterpret_cast in C++ .C++ 中使用reinterpret_cast

Let me give an example:让我举个例子:

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

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

the plain平原

pFun ptrToFunc = increase;

doesn't compile on several compilers.不能在多个编译器上编译。

I'm answering this old question because it seems that one possible solution is missing from existing answers.我正在回答这个老问题,因为现有答案中似乎缺少一种可能的解决方案。

The reason why the compiler forbids the conversion is that sizeof(void(*)(void)) can be different than sizeof(void*) .编译器禁止转换的原因是sizeof(void(*)(void))可能与sizeof(void*) We can make the function more generic, so that it can handle entries of any size:我们可以使函数更通用,以便它可以处理任何大小的条目:

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

Now the find_entry() function doesn't need to directly handle the item at all.现在find_entry()函数根本不需要直接处理项目。 Instead it just returns a pointer to the array, and the caller can cast it to a pointer-to-funcpointer before dereferencing it.相反,它只返回一个指向数组的指针,调用者可以在取消引用它之前将其转换为指向函数指针的指针。

(The code snippet above assumes the definitions from original question. You can see full code also here: try it online! ) (上面的代码片段假定原始问题的定义。您也可以在此处查看完整代码: 在线尝试!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM