GCC可以为具有标志-Wsuggest-attribute=pure
和-Wsuggest-attribute=const
的属性pure和属性const建议函数。
GCC文件中写道:
许多函数除了返回值之外没有任何效果,它们的返回值仅取决于参数和/或全局变量。这样的函数可以像算术运算符一样进行公共子表达式消除和循环优化。这些函数应该用属性pure声明。
但是,如果将__attribute__((__pure__))
附加到与上述描述不匹配且确实有副作用的函数,会发生什么?这仅仅是因为函数被调用的次数比您希望的要少,还是可能会产生未定义的行为或其他类型的严重问题?
类似地,对于再次更严格的__attribute__((__const__))
,文档说明:
基本上,这只是比下面的纯属性稍微严格一点的类,因为函数不允许读取全局内存。
但是,如果将__attribute__((__const__))
附加到一个访问全局内存的函数,那么实际上会发生什么?
我更喜欢在GCC/G++范围内对实际可能情况进行解释的技术性答案,而不是每当提到未定义的行为时就会出现的通常的"鼻恶魔"洗手。
但是如果附加
__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
的第二次调用(结果是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;
}
若用__attribute__((const))
对mypure
进行注释,编译器将再次放弃第二个调用,并优化返回到2*mypure(...)
。如果mypure
实际读取s
,这将导致产生错误的结果。
编辑
我知道你要求避免挥手,但这里有一些通用的解释。默认情况下,函数调用会阻止编译器内部的许多优化,因为它必须被视为一个可能具有任意副作用的黑盒(修改任何全局变量等)。用const或pure注释函数可以让编译器将其视为更像表达式的函数,从而实现更积极的优化。
例子实在太多了。我上面给出的是常见的子表达式消除,但我们也可以很容易地展示循环不变量、死代码消除、别名分析等的好处。