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
和m
似乎都会从代码中删除任何未使用的变量,例如-O0
。 GCC
似乎命令局部变量具有静态存储持续时间,与变量放置在堆栈上的方式相同,即从较高地址到较低地址。
因此,在clang
中,如果我们将优化级别更改为-O0
,我们会按预期打印数字2016
。
在GCC
中,如果除此之外,我们还会更改
static int* temp = &n + 1;
到
static int* temp = &n - 1;
我们还会得到代码打印的数字2016
。
答案 0 :(得分:6)
我认为这里没有任何微妙之处。 &n + 1
指向一个接一个的数组,您可以将其视为位置n
,因此它不构成可解除引用的指针,尽管它是一个完全有效的指针。因此temp
和r
是完美的constexpr变量。
您可以像这样使用r
:
for (int * p = &n; p != r; ++p) { /* ... */ }
这个循环甚至可以出现在constexpr函数中。
当您尝试取消引用r
时,行为当然是未定义的,但这与常量表达式无关。
答案 1 :(得分:4)
你显然希望你可以:
这是无稽之谈。
您必须避免所有标准支持的保证,仅依赖于UB和实施文档的不合理组合。很明显,在我们讨论有关constexpr
和std::move
的讨论之前,你已经超过了UB门槛,所以我不确定他们在这个问题上的意图是什么。
<强> Pointers are not "memory addresses" that you can use to navigate your declaration space. 强>