Почему мой второй фрагмент ниже показывает неопределенное поведение?

И то и другое clang а также g++ похоже, что он соответствует последней версии параграфа [expr.const]/5 в стандарте C++. Следующий фрагмент выводит 11 для обоих компиляторов. Смотрите живой пример:

#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? Живой пример.

#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 напечатан код.

2 ответа

Решение

Я не думаю, что здесь есть что-то тонкое. &n + 1 указывает один за концом массива один, который вы можете рассмотреть расположение nи, следовательно, он не является разыменованным указателем, хотя он является совершенно допустимым указателем. таким образом temp а также r идеально подходят переменные constexpr.

Вы могли бы использовать r как это:

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

Этот цикл может даже появиться в функции constexpr.

Поведение, конечно, не определено, когда вы пытаетесь разыменовать r, но это не имеет ничего общего с постоянными выражениями.

Вы, очевидно, ожидали, что сможете:

  • получить указатель на статический объект длительности хранения
  • добавить один к нему
  • получить указатель на "следующий" статический объект продолжительности хранения (в порядке объявления)

Это нонсенс.

Вы должны отказаться от всех стандартных гарантий, полагаясь только на нечестивую комбинацию UB и документации по реализации. Очевидно, что вы переступили порог UB задолго до того, как мы даже начнем обсуждать constexpr а также std::moveПоэтому я не уверен, какую актуальность они намеревались сохранить в этом вопросе.

Указатели не являются "адресами памяти", которые вы можете использовать для навигации по пространству объявлений.

Другие вопросы по тегам