Является ли ошибка сегментации фактическим неопределенным поведением, когда мы ссылаемся на нестатический элемент данных
Я прочитал следующее правило, и я пытался написать пример, который отражает один. Правило от 3.8/5 N3797:
До того, как началось время жизни объекта, но после того, как было выделено хранилище, которое будет занимать объект, или, после того, как закончился срок жизни объекта, и до того, как хранилище, которое занимал объект, будет повторно использовано или освобождено, любой указатель, который ссылается на хранилище Место, где будет или будет находиться объект, может использоваться, но только ограниченным образом. О строящемся или разрушаемом объекте см. 12.7. В противном случае такой указатель ссылается на выделенное хранилище (3.7.4.2) и использует указатель, как если бы указатель имел тип
void*
четко определен. Направление через такой указатель разрешено, но результирующее значение lvalue может использоваться только ограниченным образом, как описано ниже. Программа имеет неопределенное поведение, если:[...]
- указатель используется для доступа к нестатическому члену данных или для вызова нестатической функции-члена объекта, или
[...]
Пример, который я написал для:
#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
?
3 ответа
Неопределенное поведение означает, что все может произойти со стандартной соответствующей реализацией. На самом деле ничего. (и ваша точка 2 - UB)
Реализация может
- взорвать ваш компьютер и причинить вам физический вред
- сделать черную дыру, которая поглощает всю солнечную систему
- не делать ничего серьезного
- зажечь светодиод на клавиатуре
- совершите путешествие во времени и убейте всех своих дедушек и бабушек до рождения ваших собственных родителей
- так далее....
и быть соответствующим (в случае UB); Читайте также о более знакомой идее носовых демонов.
Так что то, что происходит на UB, не предсказуемо и не воспроизводимо (в общем).
Если серьезно, подумайте немного о том, что может означать UB в компьютере, подключенном к тормозам ABS вашего автомобиля, или в каком-то искусственном сердце, или в управлении какой-то атомной электростанцией.
В частности, это может иногда работать. Поскольку большинство ОС имеют ASLR, у вашего кода есть небольшой шанс работать (например, если 0xa31a3442
случайно указывает на какое-то действительное место, например, в стеке, но вы не будете воспроизводить это при следующем запуске!)
UB - это способ дать свободу разработчикам (например, компиляторам или операционным системам) и компьютерам делать все, что они "хотят", иными словами, не заботиться о последствиях. Это позволяет, например, умные оптимизации или приятные приемы реализации. Но вам следует позаботиться (и последствия могут быть другими, если вы кодируете встроенную систему управления полетом самолета, или просто несколько хакерских светодиодов демонстрационного освещения с RasberryPi, или простой пример для некоторого курса C++, работающего на Linux).
Напомним, что языковые стандарты даже не требуют какого-либо компьютера (или какого-либо оборудования) при реализации: вы можете "запустить" свой код C++ с командой человеческих рабов, но это будет крайне неэтично (и дорого, и ненадежно).
Смотрите также здесь для получения дополнительной ссылки. Вы должны по крайней мере прочитать блог Латтнера о неопределенном поведении (большая часть того, что он написал для C, относится к C++ и многим другим языкам, имеющим UB).
(добавлено в декабре 2015 г. и июне 2016 г.)
NB. Инструмент Valgrind и различные -fsanitize=
Варианты отладки для последних GCC или Clang/LLVM весьма полезны. Также включите все предупреждения и отладочную информацию в вашем компиляторе (например, g++ -Wall -Wextra -g
), и используйте соответствующие параметры инструментовки, такие как -fsanitize=undefined
, Имейте в виду, что невозможно статически и исчерпывающе обнаружить во время компиляции все случаи UB (что было бы эквивалентно проблеме остановки).
PS. Приведенный выше ответ не является специфичным для C++; это также подходит для C!
Правило 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, потому что вы не получаете доступ к объекту вне его срока действия. Вы просто используете случайную память как экземпляр.
Вы спрашивали:
Правда ли, что в случае //1 правильно сформирован и не вызывает никакого UB?
В частях стандарта, которые вы цитировали, ничего не сказано об этом.
Вы также спросили:
но //2 произвел ошибку сегментации, которая является UB?
Части указанного вами стандарта не соответствуют этому конкретному поведению. Вы видите UB из-за того, где p
точки. Это указывает на память, которая не содержит допустимый объект.