5

GCC 可以使用标志-Wsuggest-attribute=pure-Wsuggest-attribute=const.

GCC 文档说:

许多函数除了返回值外没有任何影响,它们的返回值仅取决于参数和/或全局变量。这样的函数可以像算术运算符一样进行公共子表达式消除和循环优化。这些函数应该用纯属性声明。

但是,如果您附加__attribute__((__pure__))到与上述描述不匹配且确实有副作用的函数,会发生什么?仅仅是函数被调用的次数比您希望的次数少,还是可能创建未定义的行为或其他类型的严重问题?

同样,__attribute__((__const__))哪个更严格 - 文档指出:

基本上这只是比下面的纯属性稍微严格的类,因为不允许函数读取全局内存。

但是,如果您附加到一个访问全局内存的函数,实际会发生什么呢?__attribute__((__const__))

我更喜欢在 GCC / G++ 范围内解释实际可能场景的技术答案,而不是在提到未定义行为时出现的通常的“鼻恶魔”挥手。

4

1 回答 1

5

但是,如果您附加__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()) 的第二次调用。现在想象一下,如果mypureprintf- 打印字符串的副作用"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 注释函数允许编译器将其视为表达式,从而允许更积极的优化。

例子实在是太多了,无法举出。我在上面给出的一个是常见的子表达式消除,但我们也可以很容易地证明循环不变量、死代码消除、别名分析等的好处。

于 2017-02-02T10:15:44.113 回答