Почему мой второй фрагмент ниже показывает неопределенное поведение?
И то и другое 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
Поэтому я не уверен, какую актуальность они намеревались сохранить в этом вопросе.