Когда можно получить доступ к указателю на "мертвый" объект?
во-первых, чтобы уточнить, я не про разыменование недействительного указателя!
рассмотрим следующие два примера.
Пример 1
typedef struct { int *p; } T;
T a = { malloc(sizeof(int) };
free(a.p); // a.p is now indeterminate?
T b = a; // Access through a non-character type?
Пример 2
void foo(int *p) {}
int *p = malloc(sizeof(int));
free(p); // p is now indeterminate?
foo(p); // Access through a non-character type?
вопрос
любой из приведенных выше примеров вызывает неопределенное поведение?
контекст
этот вопрос, заданный в ответ на эту дискуссию. Этот предположение состояло в том, что, например, аргументы указателя могут быть переданы функции через сегментные регистры x86, что может вызвать аппаратное исключение.
из стандарта C99, мы узнаем следующее (выделено мной):
[3.17]неопределено значение - либо неопределенное значение, либо ловушка представление
и затем:
[6.2.4 p2] значение a указатель становится неопределено когда объект на который он указывает достигает конца своей жизни.
и затем:
[6.2.6.1 p5] некоторые представления объектов не должны представлять значение типа объекта. Если сохраненное значение объекта имеет такое представление и считывается выражением lvalue, которое не имеет символьного типа,поведение не определено. Если такое представление создается a побочный эффект, который изменяет все или любую часть объекта с помощью выражения lvalue, которое не имеет символьного типа, поведение не определено. Такое представление называется ловушка представление.
принимая все это вместе, какие ограничения у нас есть на доступ к указателям на "мертвые" объекты?
дополнительное соглашение
хотя я процитировал стандарт C99 выше, мне было бы интересно узнать, отличается ли поведение любой из стандартов C++.
3 ответа:
Пример 2 недопустим. Анализ в вашем вопросе правильный.
Пример 1 является действительным. Тип структуры никогда не содержит представление ловушки, даже если это делает один из его членов. Это означает, что назначение структуры в системе, где представления ловушек вызывают проблемы, должно быть реализовано как побочная копия, а не как копия по элементам.
6.2.6 представлений типа
6.2.6.1 Генерал
6 [...] Значение объекта структуры или объединения никогда не является t rap представление, даже если значение элемента структуры или объекта объединения может быть представление ловушки.
моя интерпретация заключается в том, что, хотя только несимвольные типы могут иметь представления trap, любой тип может иметь неопределенное значение, и что доступ к объекту с неопределенным значением каким-либо образом вызывает неопределенное поведение. Самым печально известным примером может быть недопустимое использование OpenSSL неинициализированных объектов в качестве случайного семени.
Итак, ответ на ваш вопрос будет: никогда.
кстати, интересное следствие не только заостренного объекта, но и указатель будучи неопределенным после
free
илиrealloc
это то, что эта идиома вызывает неопределенное поведение:void *tmp = realloc(ptr, newsize); if (tmp != ptr) { /* ... */ }
C++ для обсуждения
короткий ответ: в C++ нет такой вещи, как доступ к "чтению" экземпляра класса; вы можете только "читать" неклассовый объект, и это делается путем преобразования lvalue-rvalue.
подробный ответ:
typedef struct { int *p; } T;
T
определяет неименованный класс. Ради обсуждения, давайте назовем этот классT
:struct T { int *p; };
поскольку вы не объявили конструктор копирования, компилятор неявно объявляет один, поэтому определение класса гласит:
struct T { int *p; T (const T&); };
Итак, мы имеем:
T a; T b = a; // Access through a non-character type?
да, действительно; это инициализация конструктором копирования, поэтому определение конструктора копирования будет сгенерировано компилятором; определение эквивалентно
inline T::T (const T& rhs) : p(rhs.p) { }
так вы получаете доступ к значению как указатель, а не кучка байтов.
если значение указателя является недопустимым (не инициализировано, освобождено), поведение не определен.