Является ли неопределенным поведением доступ к массиву за пределами границ, если я знаю, какие данные находятся по адресу, к которому осуществляется доступ?

Представьте себе следующее определение.

struct X {
    double a[8] {0.0};
    double b[8] {0.0};
}

int main() {
    X x;
    x.a[10] = 1.0;
}

Является ли поведение программы неопределенным при доступе к x.a[10]?

Обратите внимание, что термин неопределенное поведение (UB) используется в стандарте C++. Если, согласно стандарту C++, есть UB, поведение программы может быть хорошо определено реализацией на уровне машинного кода (что может быть в вашем случае).

Daniel Langr 17.01.2023 09:30

Да, это поведение undefined, потому что так сказано в стандарте. Конкретная реализация (компилятор) может делать что-то конкретное и даже делать это надежно (пока вы не обновите свой компилятор, когда он может начать делать что-то еще, потому что стандарт также не запрещает это), но это не имеет любое воздействие на то, что не определено в соответствии со стандартом.

Peter 17.01.2023 13:39

Как узнать, какие данные находятся по запрашиваемому адресу? Подсказка: в лучшем случае вы догадываетесь.

Pete Becker 17.01.2023 15:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
154
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Да. Это неопределенное поведение. Потому что компилятор может вставить пробел между a и b. Я также мог бы вставить отступ после b. Единственное, в чем вы можете быть уверены, так это в том, что перед a не будет стоять отступ.

Интересное и очень подробное описание представлено в Утерянное искусство упаковки структур.

Обновлено: Ответ Петра более точен, чем мой: «Это неопределенное поведение, потому что так сказано в стандарте». В любом случае, иногда вы чувствуете себя так: «Ну, так написано в стандарте, но на практике это всегда будет работать». Поэтому может быть полезно знать, что может произойти в реальных сценариях. Маловероятно (но возможно), что внешний доступ приведет к форматированию вашего жесткого диска, и гораздо более вероятно, что два элемента структуры не будут следовать друг за другом в памяти.

Я добавил к своему ответу (классический) пример, который показывает, что размышления о том, «как это будет работать на практике», терпят неудачу даже в довольно простых ситуациях.

Petr 17.01.2023 10:06

Это соответствует «дезинфицирующему средству неопределенного поведения», встроенному в 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 17.01.2023 09:41

Страница @Quimby не найдена, и мне бы очень хотелось посмотреть, как печатать щенков, щенков, щенков :(

mch 17.01.2023 09:43

@mch Ага, удалено, нужно 10к реп, чтобы увидеть, вот скрин.

Quimby 17.01.2023 09:47

@ Куимби, я полагаю, это неудачная шутка. Ни один компилятор, который я пробовал, не делает того, что там утверждает OP.

463035818_is_not_a_number 17.01.2023 09:49

@ 463035818_is_not_a_number Да, вероятно, но все же можно было оставить рядом...

Quimby 17.01.2023 10:09

Ваша ложная предпосылка заключается в том, что доступ за пределы будет определен, тогда, конечно, он ... не неопределен. Но это не так. Вы не можете знать, какие данные находятся на доступном адресе. Уже само понятие доступа к некоторым данным по какому-либо адресу не подходит для выражения x.a[10].

Вы предполагаете, что два массива хранятся в смежной памяти непосредственно рядом друг с другом и что арифметика указателей работает также за пределами массива. Первое неверно вообще, а второе всегда ложно.

Арифметика указателя определяется только в пределах массива.

Ваш код имеет неопределенное поведение.


Обратите внимание, что ваш код не является инструкцией для вашего процессора. Ваш код — это абстрактное описание того, что должна делать программа. А x.a[10] просто не имеет значения в языке. Это не определено. Компилятор не обязан переводить его во что-либо полезное.

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