C ++ FAQ的不安全宏的解释?

时间:2013-04-26 19:03:28

标签: c++ c c-preprocessor

According to the C++ FAQ, macros are evil

  

[9.5]为什么我应该使用内联函数而不是普通的旧#define   宏?

     

因为#define宏在4种不同的方面是邪恶的:邪恶的#1,邪恶的#2,   邪恶#3,邪恶#4。有时你应该使用它们,但它们是   还是邪恶的。        与#define宏不同,内联函数避免了臭名昭着的宏错误,因为内联函数总是精确地评估每个参数   一旦。换句话说,调用内联函数只是语义上的   比如调用常规函数,只是更快:

// A macro that returns the absolute value of i
#define unsafe(i)  \
        ( (i) >= 0 ? (i) : -(i) )

// An inline function that returns the absolute value of i
inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
  int ans;

  ans = unsafe(x++);   // Error! x is incremented twice
  ans = unsafe(f());   // Danger! f() is called twice

  ans = safe(x++);     // Correct! x is incremented once
  ans = safe(f());     // Correct! f() is called once
}
     

与宏不同,参数类型也是必需的   转换正确执行。

     

宏对你的健康有害;除非必须,否则不要使用它们。

有人可以解释为什么unsafe(x++)增加x两次?我无法理解。

4 个答案:

答案 0 :(得分:69)

通过预处理器运行它会显示问题。使用gcc -E(也可以使用cpp -P,其中-P选项也会抑制生成的#行,

inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
  int ans;

  //    increment 1      increment 2 (one of these)
  //        |             |     |
  //        V             V     V
  ans = ( (x++) >= 0 ? (x++) : -(x++) );
  ans = ( (f()) >= 0 ? (f()) : -(f()) );

  ans = safe(x++);
  ans = safe(f());
}

作为无艺术噪音注释,f()宏也会调用函数unsafe。也许它是(没有副作用)所以它本身并不是错误的。但仍然不是最理想的。

因此,因为内联函数通常比类函数宏更安全,因为它们与其他基本元素在同一语义级别上工作:变量和表达式;对于清单常量,enum通常可以更加整洁; 宏的用途是什么?

仅在编译时设置常量。您可以在编译时从命令行定义宏。而不是

#define X 12

在源文件中,您可以添加

-DX=12

cc命令。您也可以使用#undef X命令行-UX

这允许条件编译之类的东西,例如

#if X
   do this;
#else
   do that;
#endif
   while (loop);

由makefile控制,makefile本身可能是使用configure脚本生成的。

<强> X-宏即可。对于X-Macros,IMO最引人注目的用途是将enum标识符与可打印字符串相关联。虽然它起初看起来很有趣,但它减少了这些并行定义的重复和同步问题。

#define NAMES(_) _(Alice) _(Bob) _(Caravaggio) _(DuncanIdaho)
#define BARE(_) _ ,
#define STRG(_) #_ ,
enum { NAMES(BARE) };
char *names[] = { NAMES(STRG) };

请注意,您可以将宏的名称作为参数传递给另一个宏,然后使用参数调用传递的宏,就好像它本身就是一个宏(因为它 one)。有关X-Macros的更多信息,请参阅this question

答案 1 :(得分:17)

宏在编译程序之前有效地复制/粘贴

unsafe(x++)

会变成

( (x++) >= 0 ? (x++) : -(x++) )

答案 2 :(得分:10)

预处理器在编译之前替换宏。

编译器看到了这个:

  ( (x++) >= 0 ? (x++) : -(x++) )

答案 3 :(得分:4)

unsafe(x)两次评估表达式x。一旦确定其真值,然后在三元运算符的两个分支之一中第二次。内联函数safe接收一个计算参数:表达式在函数调用之前计算一次,函数调用与局部变量一起使用。

  

unsafe实际上并不像它可能那样不安全。三元运算符在评估测试和评估结果或替代表达式之间引入了一个序列点。 unsafe(x++)可靠地两次递增x,当然,问题是此行为是意外的。通常,不止一次扩展表达式的宏没有这种保证。通常,它们会产生完全未定义的行为!

大约1999年,我制作了一个库模块模块,用于捕捉带副作用的宏的使用。

因此,您可以编写“邪恶”宏并使用它们,并且机器将捕获它们被意外地与具有副作用的参数一起使用的情况(假设您有足够的代码覆盖率来在运行时达到这些用途)。

这是测试程序unsafe.c。请注意,它包含头文件sfx.h,并在SFX_CHECK的扩展令牌序列中使用unsafe宏:

#include "sfx.h"

#define unsafe(i)  \
          ( (SFX_CHECK(i)) >= 0 ? (i) : -(i) )

inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f(void)
{
  return 0;
}

int main(void)
{
  int ans;
  int x = 0;

  ans = unsafe(x++);   // Error! x is incremented twice
  ans = unsafe(f());   // Danger! f() is called twice

  ans = safe(x++);     // Correct! x is incremented once
  ans = safe(f());     // Correct! f() is called once
}

我们编译所有内容并从Linux shell提示符运行:

$ gcc unsafe.c hash.c except.c sfx.c -o unsafe
$ ./unsafe
unsafe.c:22: expression "x++" has side effects
unsafe.c:23: expression "f()" may have side effects

请注意,x++肯定有副作用,而f可能会或可能不会。因此消息的措辞不同。被调用两次的函数不一定是个问题,因为函数可能是(没有副作用)。

如果你对它的运作方式感到好奇,你可以得到here。如果启用调试,则会有运行时间损失;当然SFX_CHECK可以被禁用,因此它什么都不做(类似于assert)。

第一次评估SFX_CHECK受保护的表达式时,会对其进行解析以确定它是否有副作用。因为这种解析是在没有任何对符号表信息的访问(如何声明标识符)的情况下完成的,所以它是不明确的。解析器追求多种解析策略。使用基于setjmp/longjmp的异常处理库完成回溯。解析结果存储在键控在文本表达式上的哈希表中,以便在将来的评估中更快地检索。