【发布时间】:2013-12-01 01:31:43
【问题描述】:
在我从事的一个项目中,我们有一些实用宏,它们不止一次地引用它们的参数。
让我们用一个简单的例子:
#define ABS(a) ( (a) < 0 ? (-(a)) : (a) )
现在这是一个庞大的代码库,当我们审查代码时,我不时发现一个函数调用被传递给一个宏。 这不是错误,但这意味着函数调用被多次调用,这是我通常不希望的。
在这种情况下,我们至少可以用fabsf、fabs、abs 替换float/double/int,但我们假设并不总是有一个好的内置替换并且宏将保持为宏。
例子:
f = ABS(dot_v3v3(vel, sp));
/* expands into */
f = ( ( dot_v3v3(vel, sp) ) < 0 ? (-( dot_v3v3(vel, sp) )) : ( dot_v3v3(vel, sp) ) );
所以我的问题是:
能否检测到宏内部使用的函数调用(作为警告或错误)?
部分解决方案:
这里有一些我已经检查过的东西......
比较指针
这将导致函数调用无法编译,但缺点是像 '1' 这样的常量以及像 (b - c) 这样的表达式也会出错。
#define ABS(a) ((void)((&a) == (&a)), ( (a) < 0 ? (-(a)) : (a) ))
注意:我发现这已经很方便指出一些不好的宏使用,但由于它有误报,所以不能留下。
C11 泛型
使用_Generic,您可以将C 宏转换为内联函数的包装器。这意味着在宏中多次调用函数调用的问题消失了。
#define ABS(a) \
_Generic((a), \
long double: my_abs_double(a), \
float: my_abs_float(a), \
int: my_abs_int(a) \
/* ... and so on, char, long, short... etc */ \
)
这还不是一个可行的解决方案——我们仍然支持不支持泛型的编译器。
【问题讨论】:
-
您还必须支持没有内联函数的编译器吗?
-
我们支持带有内联函数的编译器——是的,所以我想我会得到的答案是“不要使用宏”。
-
这里的答案几乎是不要在宏中多次使用表达式。如果您的宏存在于 void 上下文中(例如,调用没有返回的函数),那么您可以将宏包装在
do { } while (0)块中并将表达式分配给局部变量。但是您的所有示例都不允许这样做。 -
@Ben Reser,当然 - 但 不要这样做 是建议 - 不是保证,此时我必须手动检查发生情况的实例,我很好奇是否有一些更可靠的方法来检查这种情况 - 至少看起来不简单(我猜你可以以某种方式计算函数调用,运行预处理器并再次计数) - 但这是一个相当繁重的解决方案。
-
你不能在宏内部使用类似:#define ABS(a) (UNIQUE_NAME=(a); UNIQUE_NAME
标签: c c-preprocessor