16

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

#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 并且不带任何参数。我的语法不正确吗?我觉得我应该能够做到这一点。

4

3 回答 3

33

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

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

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

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

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

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

J.5.7 函数指针转换

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

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

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

于 2012-12-04T06:59:25.387 回答
6

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

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

这变成&testfunc了一个指向 a 的指针void*,然后取消引用它并将 another 的值分配void*给它,这是合法的。

于 2012-12-04T06:06:55.010 回答
0

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

cmotley 在他的评论中建议的方式是正确的方法,尽管为了可读性我会建议一些改进:

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

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

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

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

编辑:更正了代码格式

于 2012-12-04T06:42:46.963 回答