Представьте себе следующее определение.
struct X {
double a[8] {0.0};
double b[8] {0.0};
}
int main() {
X x;
x.a[10] = 1.0;
}
Является ли поведение программы неопределенным при доступе к x.a[10]
?
Да, это поведение undefined, потому что так сказано в стандарте. Конкретная реализация (компилятор) может делать что-то конкретное и даже делать это надежно (пока вы не обновите свой компилятор, когда он может начать делать что-то еще, потому что стандарт также не запрещает это), но это не имеет любое воздействие на то, что не определено в соответствии со стандартом.
Как узнать, какие данные находятся по запрашиваемому адресу? Подсказка: в лучшем случае вы догадываетесь.
Да. Это неопределенное поведение. Потому что компилятор может вставить пробел между a
и b
. Я также мог бы вставить отступ после b
.
Единственное, в чем вы можете быть уверены, так это в том, что перед a
не будет стоять отступ.
Интересное и очень подробное описание представлено в Утерянное искусство упаковки структур.
Обновлено: Ответ Петра более точен, чем мой: «Это неопределенное поведение, потому что так сказано в стандарте». В любом случае, иногда вы чувствуете себя так: «Ну, так написано в стандарте, но на практике это всегда будет работать». Поэтому может быть полезно знать, что может произойти в реальных сценариях. Маловероятно (но возможно), что внешний доступ приведет к форматированию вашего жесткого диска, и гораздо более вероятно, что два элемента структуры не будут следовать друг за другом в памяти.
Я добавил к своему ответу (классический) пример, который показывает, что размышления о том, «как это будет работать на практике», терпят неудачу даже в довольно простых ситуациях.
Это соответствует «дезинфицирующему средству неопределенного поведения», встроенному в g++/clang++.
runtime error: index 10 out of bounds for type 'double [8]'
то есть доступ за пределы, о котором вы уже беспокоитесь, действительно является UB. Делать предположения о расположении памяти очень и очень рискованно, и обычно (даже если вы знаете, что делаете) результирующий код не переносим.
Обратите внимание, что в демо-версии вызов чекера происходил с помощью -fsanitize=undefined
. У нас есть отличные инструменты, давайте их использовать.
Также обратите внимание, что если вы включите проверку памяти, то есть -fsanitize=address
, предупреждение не выдается. То есть «то, что это не вызывает проблем с памятью во время выполнения, не означает, что это не UB».
Да, это поведение undefined, но не только потому, что компилятор может изменить структуру памяти X
.
Это неопределенное поведение, потому что так сказано в стандарте. В результате компилятор может делать с этим кодом что угодно: компилятор может полностью отбросить присваивание, может присвоить 1.0
всем 16 элементам, может изменить то, что делает предыдущий код, может привести к сбою программы, отформатировать жесткий диск и т. д.
Более реалистичный классический пример: следующая функция
const int table[4] = {2, 4, 6, 8};
bool exists_in_table(int v)
{
for (int i = 0; i <= 4; i++) {
if (table[i] == v) return true;
}
return false;
}
всегда возвращайте true
, по крайней мере, в современном gcc с -O3
(https://godbolt.org/z/f9cbWMYzM)
Или печатать щенки, щенки, щенки но какой-то джойкилл удалил вопрос.
Страница @Quimby не найдена, и мне бы очень хотелось посмотреть, как печатать щенков, щенков, щенков :(
@mch Ага, удалено, нужно 10к реп, чтобы увидеть, вот скрин.
@ Куимби, я полагаю, это неудачная шутка. Ни один компилятор, который я пробовал, не делает того, что там утверждает OP.
@ 463035818_is_not_a_number Да, вероятно, но все же можно было оставить рядом...
Ваша ложная предпосылка заключается в том, что доступ за пределы будет определен, тогда, конечно, он ... не неопределен. Но это не так. Вы не можете знать, какие данные находятся на доступном адресе. Уже само понятие доступа к некоторым данным по какому-либо адресу не подходит для выражения x.a[10]
.
Вы предполагаете, что два массива хранятся в смежной памяти непосредственно рядом друг с другом и что арифметика указателей работает также за пределами массива. Первое неверно вообще, а второе всегда ложно.
Арифметика указателя определяется только в пределах массива.
Ваш код имеет неопределенное поведение.
Обратите внимание, что ваш код не является инструкцией для вашего процессора. Ваш код — это абстрактное описание того, что должна делать программа. А x.a[10]
просто не имеет значения в языке. Это не определено. Компилятор не обязан переводить его во что-либо полезное.
Обратите внимание, что термин неопределенное поведение (UB) используется в стандарте C++. Если, согласно стандарту C++, есть UB, поведение программы может быть хорошо определено реализацией на уровне машинного кода (что может быть в вашем случае).