Когда это действительно для доступа к указателю на "мертвый" объект?

Во-первых, чтобы уточнить, я не говорю о разыменовании недопустимых указателей!

Рассмотрим следующие два примера.

Пример 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?

Вопрос

В любом из приведенных выше примеров вызывается поведение undefined?

Контекст

Этот вопрос задается в ответ на эту дискуссию. Предполагалось, что, например, аргументы указателя могут быть переданы функции через регистры сегментов x86, что может вызвать аппаратное исключение.

Из стандарта C99 мы узнаем следующее (основное внимание):

[3.17] неопределенное значение - либо неопределенное значение, либо представление ловушки

а затем:

[6.2.4 p2] Значение указателя становится неопределенным, когда объект, на который он указывает, достигает конца своего срока службы.

а затем:

[6.2.6.1 p5] Определенные представления объектов не обязательно должны представлять значение типа объекта. Если хранимое значение объекта имеет такое представление и считывается выражением lvalue, которое не имеет типа символа, поведение undefined. Если такое представление создается побочным эффектом, который изменяет всю или любую часть объекта выражением lvalue, которое не имеет типа символа, поведение undefined. Такое представление называется представлением ловушки.

Взяв все это вместе, какие ограничения мы имеем на доступ к указателям на "мертвые" объекты?

Добавление

Пока я цитировал вышеприведенный стандарт C99, мне было бы интересно узнать, отличается ли поведение от любого из стандартов С++.

Ответ 1

Пример 2 недействителен. Анализ в вашем вопросе правильный.

Пример 1 действителен. Тип структуры никогда не содержит ловушечного представления, даже если один из его членов выполняет. Это означает, что назначение структуры в системе, где представления ловушек вызовут проблемы, должно быть реализовано как копия, а не как отдельная копия.

6.2.6 Представления типов

6.2.6.1 Общие сведения

6 [...] Значение структуры или объекта объединения никогда не будет р представление, даже если значение члена структуры или объекта объединения может быть представление ловушки.

Ответ 2

Моя интерпретация заключается в том, что, хотя только несимвольные типы могут иметь ловушечные представления, любой тип может иметь неопределенное значение и что доступ к объекту с неопределенным значением каким-либо образом вызывает поведение undefined. Самым печально известным примером может быть недопустимое использование OpenSSL неинициализированных объектов в качестве случайного семени.

Итак, ответ на ваш вопрос будет: никогда.

Кстати, интересным следствием не только заостренного объекта, но и самого указателя после free или realloc является то, что эта идиома вызывает поведение undefined:

void *tmp = realloc(ptr, newsize);
if (tmp != ptr) {
    /* ... */
}

Ответ 3

Обсуждение на С++

Короткий ответ: В С++ нет такой вещи, как обращение к "чтению" экземпляра класса; вы можете "читать" неклассический объект, и это делается путем преобразования lvalue-to-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) {
}

Итак, вы получаете доступ к значению как указателю, а не к байту байтов.

Если значение указателя недействительно (не инициализировано, освобождено), поведение не определено.