【问题标题】:C cast void pointer to function pointerC将void指针转换为函数指针
【发布时间】:2012-11-21 17:24:50
【问题描述】:

我正在尝试创建一些可用于创建自己的单元测试库的宏。我的头文件如下所示:

#ifndef _TEST_H_
#define _TEST_H_

#include <stdio.h>
#include "hehe_stack.h"

static hehe_stack* tests;
typedef int (*testfunc)();

#define test_init() tests = hehe_stack_init();
#define test_register(test) hehe_stack_push(tests, test);
#define test_info() fprintf(stdout, "running %s :: %s \n", __FILE__, __func__);
#define test_run() testfunc = (int (*)()) hehe_stack_pop(tests); testfunc(); return 0;

#endif

在每个测试 .c 文件中,我想将一些函数指针推入测试堆栈,然后将每个函数指针从堆栈中弹出并调用它。我的堆栈弹出方法返回一个 void 指针,而我推入它的函数指针返回一个 int 并且不带参数。我的语法不正确吗?我觉得我应该能够做到这一点。

【问题讨论】:

  • 我无法想象像这样的宏在任何情况下都有意义。
  • @Lundin 在 .c 文件中实现它们会更好吗?
  • 当然。这是不可读的,会导致各种错误和麻烦。当(不是如果)出现错误时,您将很难调试它们,因为在类似函数的宏中进行调试很困难。

标签: c


【解决方案1】:

C99 标准不允许在指向数据的指针(在标准中为“对象或不完整类型”,例如 char*void*)和指向函数的指针之间进行转换。

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

一个原因是指向对象的指针和指向函数的指针不必是相同的大小。在示例架构上,前者可以是 64 位,后者可以是 32 位。

您可以将指向某个函数类型的指针转​​换为指向另一个函数类型的指针,如果您需要将函数指针存储在数据结构中,我建议您使用这种技术。任何函数类型都可以。这意味着您不能重用用于数据指针的数据结构。您需要复制数据结构并将其更改为保存函数指针。

在调用之前不要忘记转换回正确的函数指针类型,否则这是未定义的行为。

请注意,正如 Andrew Mellinger 所指出的,一些编译器允许在每个方向上进行转换。 C11的附件“J.5通用扩展”lists

J.5.7 函数指针转换

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

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

一些 POSIX 接口,例如 dlsym(),实际上也要求这些转换在 POSIX 系统的 C 编译器中有效。

【讨论】:

  • 这个代码引用真的与 void* 和函数指针之间的转换有关吗?
  • @JonathonReinhart 该子句详尽地列出了您可以使用函数指针执行的所有操作。我不认为 C 标准明确规定禁止将函数指针转换为对象指针,它只是没有将其列为允许的,所以说允许程序对函数指针做什么的子句就是我所拥有的列出。
  • @JonathonReinhart C 标准草案的 PDF 很容易获得,并且我的断言可以通过引用 C 标准中假定的 other 子句来轻松反驳,该子句将允许程序执行其他操作一个函数指针,因此欢迎您提供对那个假设的更好的子句选择的引用。如果您认为有必要,则 4:2 包含附加词“未定义的行为在本国际标准中以‘‘未定义的行为’’一词或省略任何明确的行为定义来表示。”。
  • @PascalCuoq :在 2010 年 12 月 2 日的 ISO/IEC 9899:201x 草案中,我发现:“J.5.7 函数指针转换 1 指向对象或 void 的指针可能被转换为指针到函数,允许将数据作为函数调用 (6.5.4)。2 指向函数的指针可以转换为指向对象的指针或 void,允许检查或修改函数(例如,通过调试器)(6.5.4)。”
  • @AndrewMellinger 我会将此评论合并到答案中。
【解决方案2】:

你可以这样做,但你不应该偶然这样做,所以语法变得特别尴尬。一个不强制转换函数指针,而是一个指向函数指针的指针,然后分配它。

#define test_run() *((void**)&testfunc) = hehe_stack_pop(tests); testfunc(); return 0;

这会将&amp;testfunc 转换为指向void* 的指针,然后取消引用它并将另一个void* 的值分配给它,这是合法的。

【讨论】:

  • 那不适合我。这最终起作用了:#define test_run() int (*testfunc)() = hehe_stack_pop(tests);测试函数();返回 0;
  • 这假设您已经在别处定义了testfunc。在一段代码中间定义一个变量只适用于较新版本的 C 标准。
  • 通常我会尽量避免不应该做的事情,但我遇到了一个案例,它实际上需要在我特别知道数据指针和函数指针都是 32- 的架构上进行这种转换少量。 Example code 发布在粒子社区。​​span>
【解决方案3】:

建议的代码永远不会编译,因为你不应该取消引用 void * 指针(你怎么能?没有关于特定指针的类型信息。)

cmotley 在他的评论中建议的方式是正确的方法,但为了便于阅读,我建议进行一些改进:

typedef int (*tTestFuncSignature)(void)
#define test_run() tTestFuncSignature testfunc = hehe_stack_pop(tests); testfunc();

甚至使用此宏来避免隐藏的名称冲突:

#define test_run() ((tTestFuncSignature)hehe_stack_pop(tests))();

无论哪种方式,您都必须确保(例如通过合同)您的堆栈中只有有效的指针,或者必须在调用函数之前先测试指针。

编辑:更正代码格式

【讨论】:

    猜你喜欢
    • 2011-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-20
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多