为什么我下面的第二个片段显示未定义的行为?

Why is it that my second snippet below shows undefined behavior?

clangg++ 似乎都符合 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 或显示未定义行为的答案。我总是喜欢更深入地研究,有时,就像现在一样,我碰巧足够幸运地多了解一点编译器是如何构建的。这就是我在这种情况下发现的:

对于任何大于 -O0 的优化级别,clangGCC 似乎都从代码中消除了任何未使用的变量,如 mGCC 似乎以静态存储持续时间对局部变量进行排序,与变量放置在堆栈上的方式相同,即从高地址到低地址。

因此,在 clang 中,如果我们将优化级别更改为 -O0,我们将按预期打印数字 2016

GCC中,如果除此之外,我们还更改了

的定义
static int* temp = &n + 1;

static int* temp = &n - 1;

我们也会得到代码打印的数字2016

我认为这里没有任何微妙之处。 &n + 1 指向 array-of-one 的 one-past-the-end,您可以认为位置 n,因此它不构成可取消引用的指针,尽管它是一个完全有效的指针。因此 tempr 是非常好的 constexpr 变量。

您可以这样使用 r

for (int * p = &n; p != r; ++p) { /* ... */ }

这个循环甚至可以出现在 constexpr 函数中。

当您尝试取消引用 r 时,行为当然是未定义的,但这与常量表达式无关。

您显然期望您可以:

  • 获取指向静态存储持续时间对象的指针
  • 加一
  • 获取指向 "next" 静态存储持续时间对象的指针(按声明顺序)

这是胡说八道。

您必须避开 所有 standard-backed 保证,仅依赖 UB 和实现文档的邪恶组合。很明显,在我们讨论 constexprstd::move 之前很久你就已经超过了 UB 门槛,所以我不确定他们在这个问题中的意义是什么。