将函数声明为纯函数或常量函数对GCC的影响,如果不是

时间:2017-02-02 03:27:02

标签: c++ c gcc g++ pure-function

GCC可以使用标记-Wsuggest-attribute=pure-Wsuggest-attribute=const建议属性pure和属性const的函数。

GCC documentation说:

  

除了返回值之外,许多函数都没有效果,它们的返回值仅取决于参数和/或全局变量。这样的函数可以像算术运算符那样经受公共子表达式消除和循环优化。应使用pure属性声明这些函数。

但是如果将__attribute__((__pure__))附加到与上述描述不匹配的函数并且确实有副作用会发生什么?是否只是调用函数的次数可能比你想要的少,或者是否有可能产生未定义的行为或其他类型的严重问题?

同样对于再次更严格的__attribute__((__const__)) - 文档说明:

  

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

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

我更喜欢技术答案,并解释GCC / G ++范围内的实际可能情景,而不是通常的“nasal demons”手动,只要提到未定义的行为就会出现。

1 个答案:

答案 0 :(得分:3)

  

但如果你附上__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())。现在想象一下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注释函数允许编译器更像是表达式,允许更积极的优化。

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