为什么我不能将函数指针强制转换为 (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;
无法在多个编译器上编译。
我有一个函数,它接受一个字符串、一个字符串数组和一个指针数组,并在字符串数组中查找字符串,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;
无法在多个编译器上编译。