以下代码是否有未定义的行为?
[[ gnu::pure ]]
static const MyClass &myClass() noexcept
{
static const MyClass s_myClass;
return s_myClass;
}
根据 gcc 文档,pure
属性用于除返回值外没有任何效果的函数,并且此返回值仅取决于参数和/或全局变量。
一方面,此函数除了返回值外没有任何可观察的效果,并且它始终返回相同的值。因此,优化对此函数的多次调用是完全安全的。这就是我认为pure
属性的用途。
另一方面,此函数需要在第一次调用时构造MyClass
对象。这包括调用MyClass
构造函数并将隐式初始化标志设置为 true。这可以算作返回值之外的效果(尽管从外部不可见)。
这段代码适用于gcc
,但clang
优化了MyClass
构造部分,并使myClass()
返回一个未初始化的对象。一位clang
开发人员坚持认为这是因为未定义的行为。
请参阅此错误报告:https://bugs.llvm.org/show_bug.cgi?id=36750(请注意,它说gnu::const
,但使用gnu::pure
会产生相同的结果)。
我认为静态局部变量的初始化有两个潜在的问题。
第一:
如果初始化通过引发异常退出,则初始化未完成,因此下次控件进入声明时将再次尝试
这意味着对此函数的连续调用可能具有不同的行为 - 第一次抛出,第二次没有。这似乎违背了pure
的精神和意图。
第二:
如果在初始化变量时控件并发进入声明,则并发执行应等待初始化完成。
这意味着对身体的解释必须基于功能的内在状态——需要锁定等。这似乎也违背了pure
的精神和意图。
我们要做的就是 gcc 属性的文本pure
,从这里:
纯
许多函数除了返回值之外没有任何影响,它们的返回值仅取决于参数和/或全局变量。对此类函数的调用可以像算术运算符一样进行公共子表达式消除和循环优化。这些函数应使用属性 pure 声明。例如
int square (int) __attribute__ ((pure));
说假设函数平方是安全的调用次数比程序说的少。
纯函数的一些常见示例是 strlen 或 memcmp。有趣的非纯函数是具有无限循环的函数,或者依赖于易失性内存或其他系统资源的函数,这些函数可能会在两次连续调用之间更改(例如多线程环境中的feof)。
pure 属性对函数的定义施加了与 const 属性类似但更宽松的限制:它允许函数读取全局变量。诊断同时使用 pure 和 const 属性修饰同一函数。
这比典型的 C++ 标准文本技术性低,但这是我们必须使用的。
我将列出我读到的测试:
- 除了返回值外没有任何影响
- 返回值仅取决于参数和/或全局变量
- 调用可以像算术运算符一样进行常见的子表达式消除/循环优化
- 假设函数可以安全地调用比程序所说的次数少。
其核心是消除重复调用,而不是所有调用。
不纯粹的事情的例子:
- 无限循环
- 依赖于易失性内存或其他系统资源
- 可能会在两次连续通话之间切换
在这个描述中,没有任何地方说"你可以消除对这个函数的第一次调用"——它说你可以消除对函数的重复调用。
Clang的"优化"导致函数的主体永远不会运行。[[ gnu:pure ]]
的目的是删除重复的调用,而不是消除所有调用。 因此,叮当显然是错误的。
您可能可以调用pure
属性来允许 clang 正在执行优化,但[[gnu:pure]]
不是该属性。
文档包含以下注释:
有趣的非纯函数是具有无限循环的函数,或者依赖于易失性内存或其他系统资源的函数,这些函数可能会在两次连续调用之间更改
由于静态变量构造函数和不可见的初始化标志都在函数调用之间更改内存,因此表明该函数不是纯函数。
pure
的文档还指出它类似于const
.它没有明确说明pure
,但在const
下它说:
同样,调用非
const
函数的函数通常不得const
。
由于您的函数调用的构造函数不是pure
或const
,因此它似乎可能违反了属性的规则。但请参阅下面的评论。