C++常见问题解答的不安全宏的解释?

Explanation of C++ FAQ's unsafe macro?

本文关键字:解释 不安全 问题解答 C++ 常见      更新时间:2023-10-16

根据c++常见问题解答,宏是邪恶的:

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

因为#define宏有四种不同的邪恶方式:邪恶#1,邪恶#2,邪恶三号和邪恶四号。有时候你应该用它们,但它们还是邪恶的。与#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两次?我想不出来。

通过预处理器运行它会显示出问题。使用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宏调用了两次。也许它是纯粹的(没有副作用),所以它本身并不是错误的。但仍然不够理想。

因此,由于内联函数通常比类函数宏更安全,因为它们与其他基本元素(变量和表达式)在相同的语义层上工作;对于manifest常量,enum s通常可以更整洁;宏的好的用途是什么?

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

#define X 12

在源文件中,可以添加

-DX=12

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

这允许条件编译之类的事情,例如

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

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

X-Macros 。在我看来,X-Macros最引人注目的用途是将enum标识符与可打印字符串关联起来。虽然它一开始看起来很有趣,但它减少了这些并行定义的重复和同步问题。

#define NAMES(_) _(Alice) _(Bob) _(Caravaggio) _(DuncanIdaho)
#define BARE(_) _ ,
#define STRG(_) #_ ,
enum { NAMES(BARE) };
char *names[] = { NAMES(STRG) };
请注意,您可以将宏的名称作为参数传递给另一个宏,然后通过使用参数调用传递的宏,就好像本身就是一个宏一样(因为就是一个宏)。有关x -宏的更多信息,请参阅这个问题。

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

unsafe(x++)

将成为

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

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

编译器看到:

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

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

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

大约在1999年,我制作了一个库模块模块,用于捕获具有副作用的宏的使用。

因此,您可以编写"邪恶的"宏并使用它们,并且机器将捕获它们意外地与具有副作用的参数一起使用的情况(前提是您有足够的代码覆盖率来在运行时处理这些使用)。

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

#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可能有也可能没有。所以这些信息的措辞是不同的。函数被调用两次不一定是问题,因为函数可能是纯的(没有副作用)。

如果你对它的工作原理感兴趣,你可以在这里找到。如果启用调试,则存在运行时罚金;当然,SFX_CHECK可以被禁用,所以它什么也不做(类似于assert)。

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