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 * 和函数指针的主要内容,如果未能解决你的问题,请参考以下文章

C/C++语法和用法技巧(指针)

c# 调用c dll void 指针类型转化问题

C语言关于指针函数与函数指针个人理解

c语言指针详解 看完必有收获!!!!

void函数指针的二维数组c ++

C语言中函数指针