Rio*_*iot 5 c c++ gcc g++ pure-function
GCC 可以使用标志-Wsuggest-attribute=pure和为属性 pure 和属性 const 建议函数-Wsuggest-attribute=const。
在海湾合作委员会的文件说:
许多函数除了返回值外没有任何影响,它们的返回值仅取决于参数和/或全局变量。这样的函数可以像算术运算符一样进行公共子表达式消除和循环优化。这些函数应该用属性 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;
}
Run Code Online (Sandbox Code Playgroud)
我的 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;
}
Run Code Online (Sandbox Code Playgroud)
这两个调用都将被发出(因为赋值给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
}
Run Code Online (Sandbox Code Playgroud)
编译器很可能会放弃第二次调用get_index()并使用第一个返回值-1,这将导致缓冲区溢出(好吧,技术上的下溢)。
但是,如果您附加
__attribute__((__const__))到访问全局内存的函数,实际会发生什么?
让我们再次以上面的例子为例
int call_pure() {
int x = mypure("Hello");
s[0] = 1;
int y = mypure("Hello");
return x + y;
}
Run Code Online (Sandbox Code Playgroud)
如果用mypure注释__attribute__((const)),编译器将再次删除第二个调用并优化返回到2*mypure(...). 如果mypure实际读取s,这将导致产生错误的结果。
编辑
我知道你要求避免挥手,但这里有一些通用的解释。默认情况下,函数调用会阻止编译器内部的许多优化,因为它必须被视为可能具有任意副作用(修改任何全局变量等)的黑匣子。相反,使用 const 或 pure 注释函数允许编译器将其更像是表达式,从而允许更积极的优化。
例子实在是太多了,无法给出。我上面给出的一个是常见的子表达式消除,但我们也可以很容易地展示循环不变量、死代码消除、别名分析等的好处。
| 归档时间: |
|
| 查看次数: |
844 次 |
| 最近记录: |