C++常见问题解答的不安全宏的解释?
Explanation of C++ FAQ's unsafe macro?
根据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
的异常处理库完成的。解析结果存储在以文本表达式为关键字的散列表中,以便在以后的计算中更快地检索。
- C++/CLI 和 C#/VB 与不安全和外部有什么区别?
- 问:Apache Arrow 数组生成器不安全追加
- 不安全的 MPI 非阻塞通信示例?
- 有没有一种简单的方法来检查C++中的不安全表达式
- 为什么静态向下转换unique_ptr不安全?
- 哪些整数操作不安全
- 为什么这个递归 lambda 函数不安全?
- 解决方法:QPixmap:在GUI线程之外使用pixmap是不安全的
- 正在匹配不安全的正则线程
- 如何修复编译错误"此函数或变量可能不安全"(strcpy)
- 编译器在 const ref 类型参数上使用临时对象时是否应该警告不安全的行为?
- 实现没有不安全服务器凭据的自定义 AuthMetadataProcessor
- 什么时候关闭__strict_ansi__标志是不安全的
- 原子对象在普通对象安全的任何上下文中都是不安全的
- OpenSSL:将不安全的BIO提升为安全
- 这是对支撑初始器列表的不安全使用情况
- 从 C# 到C++和返回的数组,没有不安全的代码
- 一个线程设置成员,而另一个循环上方 - 是此螺纹 - 不安全
- 在void*和void*之间进行强制转换时,混合使用静态强制转换和重新解释强制转换是否不安全?
- C++常见问题解答的不安全宏的解释?