clang
和 g++
似乎都符合 C++ 标准中段落 [expr.const]/5 的最新版本。以下代码段为两个编译器打印 11。参见 live example :
#include <iostream>
void f(void) {
static int n = 11;
static int* temp = &n;
static constexpr int *&&r = std::move(temp);
std::cout << *r << '\n';
}
int main()
{
f();
}
根据我对这一段的理解,两个编译器都应该为下面的代码打印 2016
。但他们没有。因此,我必须得出结论,该代码显示了未定义的行为,因为 clang
打印了一个任意数字,而 g++
打印了 0
。我想知道为什么是 UB,例如,考虑到标准的 N4527 草案? Live example .
#include <iostream>
void f(void) {
static int n = 11;
static int m = 2016;
static int* temp = &n + 1;
static constexpr int *&&r = std::move(temp);
std::cout << *r << '\n';
}
int main()
{
f();
}
编辑
我习惯于不满足于只说代码是 UB 或显示未定义行为的答案。我总是喜欢更深入地研究,有时,就像现在一样,我碰巧足够幸运地多了解一点编译器是如何构建的。这就是我在这种情况下发现的:
对于任何大于 的优化级别,
。 clang
和 GCC
似乎都从代码中消除了任何未使用的变量,例如>-O0GCC
似乎对具有静态存储持续时间的局部变量进行排序,这与变量放置在堆栈上的方式相同,即从高地址到低地址。
因此,在 clang
中,如果我们将优化级别更改为 -O0
,我们将按预期打印出数字 2016
。
在GCC
中,如果除此之外,我们还更改了
static int* temp = &n + 1;
到
static int* temp = &n - 1;
我们还将得到代码打印的数字2016
。
最佳答案
我认为这里没有任何微妙之处。 &n + 1
指向一个数组的最后一个,您可以将其视为位置 n
,因此它不构成可取消引用的指针,尽管它是一个完全有效的指针。因此 temp
和 r
是非常好的 constexpr 变量。
你可以像这样使用 r
:
for (int * p = &n; p != r; ++p) { /* ... */ }
这个循环甚至可以出现在 constexpr 函数中。
当您尝试取消引用 r
时,行为当然是未定义的,但这与常量表达式无关。
关于c++ - 为什么我下面的第二个片段显示未定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34556422/