【问题标题】:Checking function calls are not passed as arguments (C macros)检查函数调用不作为参数传递(C 宏)
【发布时间】: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


【解决方案1】:

我确实在MirOS C Preprocessor manual in the section on duplicating side effects 中找到了这个技巧:

 #define min(X, Y)                \
 ({ typeof (X) x_ = (X);          \
    typeof (Y) y_ = (Y);          \
    (x_ < y_) ? x_ : y_; })

【讨论】:

  • 这将解决这个问题,但请注意它依赖于两个 gcc 扩展(不过,clang 和 icc 也支持这些扩展)
  • 即使它使用 gcc/icc/clang 扩展,这可能是最接近答案的东西。
  • 是的...这也处理像 MIN(i--,j++) 这样的 incr / decr 的情况
猜你喜欢
  • 2015-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-25
  • 2020-02-12
  • 2016-01-21
相关资源
最近更新 更多