但是,如果您附加__attribute__((__pure__))
到与上述描述不匹配且确实有副作用的函数,会发生什么?
确切地。这是一个简短的示例:
extern __attribute__((pure)) int mypure(const char *p);
int call_pure() {
int x = mypure("Hello");
int y = mypure("Hello");
return x + y;
}
我的 GCC (4.8.4) 版本足够聪明,可以删除对mypure
(result is 2*mypure()
) 的第二次调用。现在想象一下,如果mypure
是printf
- 打印字符串的副作用"Hello"
将会丢失。
请注意,如果我替换call_pure
为
char s[];
int call_pure() {
int x = mypure("Hello");
s[0] = 1;
int y = mypure("Hello");
return x + y;
}
两个调用都将被发出(因为赋值s[0]
可能会改变 的输出值mypure
)。
仅仅是函数被调用的次数比您希望的次数少,还是可能创建未定义的行为或其他类型的严重问题?
那么,它可以间接导致UB。例如这里
extern __attribute__((pure)) int get_index();
char a[];
int i;
void foo() {
i = get_index(); // Returns -1
a[get_index()]; // Returns 0
}
编译器很可能会放弃第二次调用get_index()
并使用第一个返回值-1
,这将导致缓冲区溢出(嗯,技术上的下溢)。
但是,如果您附加__attribute__((__const__))
到一个访问全局内存的函数,实际会发生什么呢?
让我们再次以上面的例子为例
int call_pure() {
int x = mypure("Hello");
s[0] = 1;
int y = mypure("Hello");
return x + y;
}
如果mypure
用 注释__attribute__((const))
,编译器将再次放弃第二次调用并优化返回到2*mypure(...)
. 如果mypure
实际读取s
,这将导致产生错误的结果。
编辑
我知道您要求避免挥手,但这里有一些通用的解释。默认情况下,函数调用会阻止编译器内部的许多优化,因为它必须被视为可能具有任意副作用(修改任何全局变量等)的黑盒。使用 const 或 pure 注释函数允许编译器将其视为表达式,从而允许更积极的优化。
例子实在是太多了,无法举出。我在上面给出的一个是常见的子表达式消除,但我们也可以很容易地证明循环不变量、死代码消除、别名分析等的好处。