【问题标题】:Effects of declaring a function as pure or const to GCC, when it isn't将函数声明为纯函数或 const 对 GCC 的影响,当它不是时
【发布时间】:2017-02-02 03:27:01
【问题描述】:

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

GCC documentation 说:

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

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

__attribute__((__const__)) 也同样严格——文档指出:

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

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

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

【问题讨论】:

  • 请确保如果您对编译器撒谎,它会得到自己的回报。
  • @Jonathan Leffler 提醒我 Babbage 引用关于人们询问他的“引擎”是否会在所问问题错误时给出正确答案。

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


【解决方案1】:

但是如果你附上__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 注释函数可以让编译器将其视为表达式,从而进行更积极的优化。

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

【讨论】:

  • 感谢您的详细回答。不过,您对这些信息有任何参考吗?如果我正确理解了您的解释,那么您是在暗示 pure 和 const 属性基本上具有相同的效果,这只是省略了重复的调用。那么,为什么在 const 函数中访问全局内存会受到限制 - 可能的影响是什么?我对 gcc 如何处理这些属性的细节非常感兴趣(因为关于这个主题的文档很少),而不是推测这些属性在理论上可能做什么。
  • @Riot 我认为您不会找到关于此的详尽文档。 GCC 中的许多优化过程都将 const/pure 信息考虑在内(对源进行 grepping 会导致中间端有 38 个文件,包括循环不变量、死代码消除等)。基本上纯/常量属性通知编译器调用不会有副作用,然后可以用于大量优化。我只给出了一个简单的例子——常见的子表达式消除(这是一种经典的优化技术)。
  • @Riot “为什么...限制在 const 函数中访问全局内存” - 好吧,这不是限制,而是对编译器的又一个提示,允许它进一步优化对 const 函数的调用积极地(即将它们移过可能修改全局内存的语句)。同样,示例实在太多了,基本上任何编译器优化都会受益于 const/pure 属性。
  • 感谢您的澄清。所以文档的声明“函数不允许读取全局内存”实际上只是意味着可能发生的最坏情况是这种内存访问可能发生在不同的时间,在内存栅栏的错误一侧等?以这种方式指定“不允许”让我有点奇怪,而 gcc 没有尝试以任何方式强制执行,或者如果发生警告 - 我特别好奇意外将这些属性附加到函数可能引入的错误不履行文档中指定的义务。
  • @Riot “可能发生的最坏情况是这种内存访问可能发生在不同的时间” - 最糟糕的是编译器在写入它应该读取的全局内存之前移动纯函数调用。这很可能会导致纯函数产生不正确的结果。
猜你喜欢
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-08
相关资源
最近更新 更多