ISO C Void * 和函数指针
Posted
技术标签:
【中文标题】ISO C Void * 和函数指针【英文标题】:ISO C Void * and Function Pointers 【发布时间】:2012-12-17 12:57:37 【问题描述】:在阅读一些教程并阅读有关函数指针的内容时,我了解到在 ISO C 中将 void 指针分配给函数指针显然是未定义的,有没有办法解决我在编译期间收到的警告(例如,一种更好的编码方式它)还是我应该忽略它?
警告:
ISO C forbids assignment between function pointer and 'void *' [-pedantic]
示例代码:
void *(*funcPtr)();
funcPtr = GetPointer();
GetPointer 是一个返回空指针 E.G. 的函数
void *GetPointer();
【问题讨论】:
不给函数指针分配一个void指针怎么样? 理论上未定义,因为有些机器过去代码地址和数据地址的大小不同。实际上,在当今最常见的架构中,代码和数据地址的大小相同,位于相同的地址空间中。 @RobertHarvey:一个常见的例子是 Posix 中的dlsym
。您必须将其结果分配给函数指针(早期的标准草案提出了一些其他名称,可能是dlfsym
,以返回函数指针,但从未发生过)
@JonathanWood GetPointer 返回一个 void* 我在原始帖子中没有注意到这一点。
@JonathanWood Casting 并没有让它变得更好,因为 conversion 是被禁止的,而不仅仅是 implicit 转换。 所以这包括 显式转换(强制转换)。
【参考方案1】:
在tlpi-book我发现这个技巧很有趣:
#include <dlfcn.h>
int
main(int argc, char *argv[])
...
void (*funcp)(void); /* Pointer to function with no arguments */
...
*(void **) (&funcp) = dlsym(libHandle, argv[2]);
【讨论】:
@MatthieuPoullet:在右手边,我们有一个空指针(void *
)。在左侧,我们获取函数指针的地址。暂时忽略(void **)
,指向函数指针的指针被最左边的*
取消引用。 (void **)
只是说指向函数指针的指针应该转换为指向 void 指针的指针,当被最左边的 *
取消引用时,它因此是 void *
。因此,赋值的左侧和右侧都有void *
类型,编译器很高兴。
这是一个严格的别名违规,因此具有未定义的行为。即使使用memcpy
解决了这个问题,我们仍然会依赖我们不应该依赖的东西,即函数指针的二进制表示与数据指针的表示相同。最好只做一个普通的演员表并在本地禁用-Wpedantic
。我不建议通过做一些更糟糕的事情来解决警告,而编译器恰好没有警告。【参考方案2】:
没有。编译器是对的,你也是:在 C89 和 C99 中,你不能在数据指针(void *
是)和函数指针之间进行转换,所以解决警告的唯一方法是从函数返回一个函数指针。
(但是请注意,尽管有警告,但实际上这仍然有效,即使标准库中存在这种不一致 - dlsym()
函数用于获取函数指针,但它返回 void *
- 所以基本上你可以忽略警告。它会起作用,但严格来说,这里的行为是未定义的。)
【讨论】:
确实代码有效 我只是想了解更好的可能的编码风格(这就是为什么我使用如此严格的警告进行编码),与其说是没有警告,不如说是了解它们的原因、影响和避免它们的方法。 @Tomwaivory 是的,我明白了,这绝对是一个好习惯。在这种情况下,这是语言的问题:) 所以不用担心,实践与理论不一样,除非你在 DS9k 上工作,否则它可以在里面使用这个 UB,你可以打败C标准委员会的成员用棍子:D 谢谢,很高兴知道,我无法修复语言,只是尽力围绕它编写代码 :) “在实践中这个工作”在大多数架构上。有一些(主要是晦涩难懂的)架构不起作用。dlsym
也不是“标准库”函数,它是 Unix 库函数。这不是“语言有问题”,因为这是有原因的(在某些地方它不起作用)。
@newacct 请不要把头发分开。你说得对。要求 C 强加的设计考虑到了可移植性 - 但是其中一些现在非常不常见或本地化。 “dlsym()
不是标准库函数”——它只是不是 ISO/ANSI C 标准库的一部分,而是 POSIX C 标准库的一部分。【参考方案3】:
我在使用 glib 时遇到了这个问题。 Glib 数据结构,例如 GSList 通常有一个称为 void *data 的字段。我想将函数存储在一个列表中,但遇到了一堆类似这样的错误:
warning: ISO C forbids passing argument 2 of ‘g_slist_append’ between function pointer and ‘void *’ [-pedantic]
这个例子使用 gcc -Wall -ansi -pedantic 生成一堆警告
typedef int (* func) (int);
int mult2(int x)
return x + x;
int main(int argc, char *argv[])
GSList *functions = NULL;
func f;
functions = g_slist_append(functions, mult2);
f = (func *) functions->data;
printf("%d\n", f(10));
return 0;
所以我将函数包装在一个结构中,所有警告都消失了:
struct funcstruct
int (* func) (int);
;
int mult2(int x)
return x + x;
int main(int argc, char *argv[])
GSList *functions = NULL;
struct funcstruct p;
p.func = mult2;
functions = g_slist_append(functions, &p);
p = * (struct funcstruct *) functions->data;
printf("%d\n", p.func(10));
return 0;
有争议的是,这是使一些警告消失的相当多的额外代码,但我不喜欢我的代码生成警告。此外,以上是玩具示例。在我正在编写的实际代码中,将函数列表包装在结构中是非常有用的。
我很想知道这是否有问题,或者是否有更好的方法。
【讨论】:
如果这样做,则不能使用p
范围之外的列表,因为指向p
的指针只在p
范围内有效。因此,您的清单将毫无用处。并且完全没有必要制作结构类型。如果您只是简单地将p
声明为int (* p) (int);
,它将与您现在所拥有的几乎相同。以上是关于ISO C Void * 和函数指针的主要内容,如果未能解决你的问题,请参考以下文章