迈尔斯与[[gnu :: pure]]一起使用的单身人士是否有UB?

时间:2018-03-16 17:18:09

标签: c++ gcc clang language-lawyer

以下代码是否有未定义的行为?

[[ gnu::pure ]]
static const MyClass &myClass() noexcept
{
    static const MyClass s_myClass;
    return s_myClass;
}

根据gcc docspure属性用于除返回值之外没有任何效果的函数,此返回值仅取决于参数和/或全局变量。

一方面,除了返回值之外,此函数没有任何可观察的效果,并且它总是返回相同的值。因此,优化多次调用此函数是完全安全的。这是我认为pure属性的用途。

另一方面,此函数需要在第一次调用时构造MyClass对象。这包括调用MyClass构造函数并将隐式 is-initialised 标志设置为true。这可以算作除了返回值之外的效果(尽管从外面看不到它)。

此代码适用于gcc,但clang优化了MyClass构造部分,并使myClass()返回未初始化的对象。 clang开发人员坚持认为这是因为未定义的行为。

请参阅此错误报告:https://bugs.llvm.org/show_bug.cgi?id=36750(请注意gnu::const,但使用gnu::pure会产生相同的结果。)

3 个答案:

答案 0 :(得分:5)

我认为初始化静态局部变量有两个潜在的问题。

First

  

如果初始化通过抛出异常退出,则初始化未完成,因此下次控件进入声明时将再次尝试

这意味着对此函数的连续调用可能会有很多不同的行为 - 第一次抛出而第二次不抛出。这似乎违反了pure的精神和意图。

Second

  

如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成。

这意味着对身体的解释必须基于功能的内在状态 - 需要锁定等。这似乎也违反了pure的精神和意图。

答案 1 :(得分:5)

我们需要继续的是来自here的gcc属性pure的文字:

  

     

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

int square (int) __attribute__ ((pure));
     

说假设的函数square可以安全地调用比程序说的更少的次数。

     

纯函数的一些常见示例是strlen或memcmp。有趣的非纯函数是具有无限循环的函数或取决于易失性存储器或其他系统资源的函数,这些函数可能在两个连续调用之间发生变化(例如多线程环境中的feof)。

     

纯属性对函数的定义施加类似但更松散的限制,而不是const属性:它允许函数读取全局变量。用pure和const属性装饰相同的函数被诊断出来。

这不像典型的标准文本那样具有技术性,但这是我们必须使用的。

我将列出我读过的测试:

  • 除了返回值
  • 之外没有任何效果
  • 返回值仅取决于参数和/或全局变量
  • 调用可以像算术运算符一样受到公共子表达式消除/循环优化的影响
  • 假设的功能可以安全地拨打少于该程序所说的次数。

这样做的核心是消除重复的电话,而不是所有电话。

不纯粹的事情的例子:

  • 无限循环
  • 取决于易失性内存或其他系统资源
  • 可能会在两次连续通话之间切换

在这个描述中没有说“你可以取消对该函数的第一次调用” - 它表示你可以消除对该函数的重复调用。

Clang的“优化”导致函数体永不运行。 [[ gnu:pure ]]的目的是删除重复的调用,而不是消除所有调用。因此,铿锵显然是错误的。

有些属性可以调用pure来允许优化clang正在执行,但[[gnu:pure]]不是该属性。

答案 2 :(得分:-1)

文档包含此注释:

  

有趣的非纯函数是具有无限循环的函数或取决于易失性内存或其他系统资源的函数,这些函数可能在两次连续调用之间发生变化

由于静态变量构造函数和不可见的 is-initialized 标志都在改变函数调用之间的内存,因此它表明该函数不是纯粹的。

pure的文档也指出它与const类似。它并没有在pure下明确说明这一点,但在const下它说:

  

同样,调用非const函数的函数通常不能是const

由于您的函数正在调用非pureconst的构造函数,因此它似乎可能违反了属性的规则。但请参阅下面的评论。