Является ли фактическая ошибка сегментации undefined, когда мы ссылаемся на нестатический элемент данных

Я прочитал следующее правило, и я пытался написать пример, который отражает его. Правило от 3.8/5 N3797:

До начала жизни объекта, но после хранения который объект будет занимать, был выделен или, после жизни объекта и до хранения, который объект занято повторно или освобождено, любой указатель, который ссылается на хранилище местоположение, в котором объект будет находиться или находится, может использоваться, но только ограниченным образом. Для объекта, находящегося в стадии строительства или уничтожения, см. 12,7. В противном случае, такой указатель относится к выделенному хранилищу (3.7.4.2) и с использованием указателя, как если бы указатель имел тип void*, был корректно определен. Разрешено использование такого указателя, но полученное значение lvalue может использоваться только ограниченным образом, как описано ниже. Программа имеет undefined поведение, если:

[...]

- указатель используется для доступа к нестатическому элементу данных или вызова нестатическая функция-член объекта или

[...]

Пример, который я написал для:

#include <iostream>
#include <typeinfo>

using std::cout;
using std::endl;

struct A
{
    int b = 5;
    static const int a = 5;
};

int main()
{
    A *p = (A*)0xa31a3442;
    cout << p -> a; //1, Well-fromed, there is no compile-time error
    cout << p -> b; //2, Segmentation fault is producing
}

Верно ли, что в случае //1 хорошо сформировано и не вызывает никаких UB, но //2 возникла ошибка сегментации, которая равна UB?

Ответы

Ответ 1

Undefined поведение означает, что что-то может произойти со стандартной соответствующей реализацией. На самом деле ничего. (а ваша точка 2 - UB)

Реализация может

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

и соответствовать (в случае UB); читайте также о более знакомой идее носовых демонов.

Итак, что происходит на UB, не предсказуемо и не воспроизводится (вообще).

Серьезно подумайте немного о том, что может означать UB на компьютере, подключенном к тормозам ABS вашего автомобиля, или в каком-то искусственном сердце, или за рулем какой-либо атомной электростанции.

В частности, он может работать иногда. Поскольку большинство ОС имеют ASLR, ваш код имеет крошечный шанс на работу (например, если 0xa31a3442 указывает на какое-то допустимое местоположение, например, в стеке, но вы не будете воспроизводить это при следующем прогоне!)

UB - это способ предоставить свободу исполнителям (например, компиляторам или операционным системам) и компьютерам делать то, что они хотят, другими словами, не заботятся о последствиях. Это позволяет, например, умные оптимизации или хорошие трюки реализации. Но вы должны заботиться (и последствия разные, если вы кодируете встроенную систему управления полетом самолета или просто какие-то взломанные светодиодные светодиодные индикаторы с RasberryPi или простой пример для некоторых курсов С++, работающих на Linux).

Вспомним, что языковые стандарты даже не требуют какого-либо компьютера (или какого-либо оборудования) в реализации: вы можете "запустить" свой код на С++ с командой человеческих рабов, но это было бы крайне неэтично (и дорогостоящим, и ненадежным).

См. также здесь для получения дополнительных ссылок. Вы должны хотя бы прочитать блог Lattner в Undefined Behavior (большинство из того, что он написал для C, относится к С++ и многим другим языкам, имеющим UB).


(добавлено в декабре 2015 и июне 2016 года)

NB. Инструмент valgrind и различные опции -fsanitize= отладки для недавних GCC или Clang/LLVM весьма полезны, Кроме того, включите все предупреждения и информацию об отладке в своем компиляторе (например, g++ -Wall -Wextra -g) и используйте соответствующие параметры инструментария, такие как -fsanitize=undefined. Имейте в виду, что невозможно установить статически и исчерпывающе во время компиляции все случаи UB (что было бы эквивалентно Проблема с остановкой).

PS. Вышеприведенный ответ не относится к С++; он также подходит для C!

Ответ 2

Правило 3.8/5 - это время вне конструкции/уничтожения объекта, но внутри выделения/освобождения памяти, в которой находится объект. Ниже демонстрируются точки вне времени жизни объекта:

void *buffer = malloc(sizeof(A));
// outside of lifetime of a
// a->b is undefined
A* a = new (buffer) A();
// within lifetime of a
// a->b is valid
a->~A();
// outside of lifetime of a
// a->b is undefined
free(buffer);

Технически, ваш пост на самом деле не отражает правило 3.8/5, потому что вы не получаете доступ к объекту за пределами его срока службы. Вы просто бросаете случайную память в качестве экземпляра.

Ответ 3

Вы спросили:

Верно ли, что в случае, если // 1 корректно сформирован и не вызывает никакого UB?

Части стандарта, который вы указали, ничего не говорят об этом.

Вы также спросили:

но // 2 вызвала ошибку сегментации, которая является UB?

Части указанного вами стандарта не соответствуют этому конкретному поведению. Вы видите UB из-за того, где p указывает. Он указывает на память, которая не содержит допустимый объект.