二者clang
并g++
似乎是符合在C++标准的段落[expr.const]/5的最后一个版本.以下代码段为两个编译器打印11.查看实例:
#includevoid 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草案?实例.
#includevoid 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
似乎以消除任何未使用的变量,比如m
,从代码,对任何优化级别大于-O0
.GCC
似乎用静态存储持续时间对局部变量进行排序,变量放在堆栈上的方式相同,即从高地址到低地址.
因此,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
,行为当然是未定义的,但这与常量表达式无关.