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

时间:2016-01-01 13:09:37

标签: c++ language-lawyer constexpr c++17

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的答案,或者显示未定义的行为。我总是喜欢进行更深入的调查,有时候,就像现在一样,我很幸运能够更多地了解编译器是如何构建的。这就是我在这种情况下发现的:

对于任何大于clang的优化级别,GCCm似乎都会从代码中删除任何未使用的变量,例如-O0GCC似乎命令局部变量具有静态存储持续时间,与变量放置在堆栈上的方式相同,即从较高地址到较低地址。

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

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

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

static int* temp = &n - 1;

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

2 个答案:

答案 0 :(得分:6)

我认为这里没有任何微妙之处。 &n + 1指向一个接一个的数组,您可以将其视为位置n,因此它不构成可解除引用的指针,尽管它是一个完全有效的指针。因此tempr是完美的constexpr变量。

您可以像这样使用r

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

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

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

答案 1 :(得分:4)

你显然希望你可以:

  • 获取指向静态存储持续时间对象的指针
  • 添加一个
  • 指向&#34; next&#34;静态存储持续时间对象(按声明顺序)

这是无稽之谈。

您必须避免所有标准支持的保证,仅依赖于UB和实施文档的不合理组合。很明显,在我们讨论有关constexprstd::move的讨论之前,你已经超过了UB门槛,所以我不确定他们在这个问题上的意图是什么。

<强> Pointers are not "memory addresses" that you can use to navigate your declaration space.