Подтвердить что ты не робот

Является ли законным сравнивать висячие указатели?

Является ли законным сравнивать висячие указатели?

int *p, *q;
{
    int a;
    p = &a;
}
{
    int b;
    q = &b;
}
std::cout << (p == q) << '\n';

Обратите внимание, как обе p и q указывают на объекты, которые уже исчезли. Является ли это законным?

4b9b3361

Ответ 1

Введение:. Первая проблема заключается в том, законно ли использовать значение p вообще.

После того, как a был уничтожен, p получает то, что известно как недопустимое значение указателя. Цитата из N4430 (для обсуждения статуса N4430 см. "Примечание" ниже):

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

Поведение, когда используется недопустимое значение указателя, также рассматривается в том же разделе N4430 (и почти идентичный текст появляется в С++ 14 [basic.stc.dynamic.deallocation]/4):

Отказ от недопустимого значения указателя и передача недопустимого значения указателя функции деаллокации имеют поведение undefined. Любое другое использование недопустимого значения указателя имеет поведение, определенное реализацией.

[Сноска: некоторые реализации могут определять, что копирование недопустимого значения указателя вызывает системную ошибку выполнения. - конец сноски]

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

Термин, используемый в приведенных выше цитатах означает, требующий преобразования lvalue-to-rvalue, как в С++ 14 [conv.lval/2]:

Когда преобразование lvalue-rvalue применяется к выражению e, а [...] объект, к которому относится ссылка glvalue, содержит недопустимое значение указателя, поведение определяется реализацией.


История:. В С++ 11 этот undefined, а не определенный реализацией; он был изменен на DR1438. См. Историю изменений этого сообщения для полных кавычек.


Приложение к p == q: Предположим, что мы приняли в С++ 14 + N4430, что результат оценки p и q определяется реализацией и что реализация не определить, что происходит аппаратная ловушка; [expr.eq]/2 говорит:

Два указателя сравнивают одинаковые, если оба они равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес (3.9.2), в противном случае они сравниваются неравномерно.

Так как он определяет реализацию, какие значения получаются при оценке p и q, мы не можем точно сказать, что произойдет здесь. Но он должен быть либо определенным, либо неопределенным.

g++ проявляет неуказанное поведение в этом случае; в зависимости от коммутатора -O я мог сказать, что он либо 1, либо 0, соответствующий тому, был ли повторно сохранен тот же адрес памяти для b после a.


Примечание о N4430: Это предлагаемое разрешение дефекта для С++ 14, которое еще не принято. Он очищает много формулировок, связанных с временем жизни объекта, недействительными указателями, подобъектами, объединениями и доступом к границам массива.

В тексте С++ 14 в разделе [basic.stc.dynamic.deallocation]/4 и последующих абзацах определено недопустимое значение указателя, когда используется delete. Однако четко не указано, применяется ли тот же принцип к статическому или автоматическому хранению.

В [basic.compound]/3 есть определение "действительный указатель", но оно слишком смутно для разумного использования. [basic.life]/5 (сноска) относится к одному и тому же тексту для определения поведения указателей к объектам статической продолжительности хранения, что предполагает, что он предназначен для применения ко всем типам хранилищ.

В N4430 текст перемещается из этого раздела на один уровень, так что он явно применяется ко всем длинам хранения. Прилагается примечание:

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


Мое мнение: я не вижу последовательного способа интерпретации стандарта (pre-N4430), кроме как сказать, что p получает недопустимое значение указателя. Поведение, похоже, не охватывает какой-либо другой раздел, кроме того, что мы уже рассмотрели. Поэтому я с удовольствием отношусь к формулировке N4430 как представляющей намерение стандарта в этом случае.


Ответ 2

Исторически существовали некоторые системы, в которых использование указателя в качестве значения r может привести к тому, что система будет извлекать некоторую информацию, идентифицированную некоторыми битами в этом указателе. Например, если указатель может содержать адрес заголовка объекта вместе со смещением в объекте, выборка указателя может заставить систему также получить некоторую информацию из этого заголовка. Если объект перестает существовать, попытка получить информацию из его заголовка может завершиться с произвольными последствиями.

Что было сказано в подавляющем большинстве реализаций C, все указатели, которые были живы в определенный момент времени, навсегда будут поддерживать те же отношения в отношении операторов реляционных и вычитаний, какие они имели в это конкретное время. В самом деле, в большинстве реализаций, если есть char *p, можно определить, идентифицирует ли он часть объекта, идентифицированного char *base; size_t size;, путем проверки того, (size_t)(p-base) < size; такое сравнение будет работать даже ретроспективно, если есть какое-либо совпадение в жизни объекта.

К сожалению, стандарт не определяет, каким образом код может указывать на то, что он требует какой-либо из последних гарантий, а также нет стандартного средства, с помощью которого код может спросить, может ли конкретная реализация обещать какое-либо из последних действий и отказаться от компиляции, если Это не. Кроме того, некоторые гиперсовременные реализации будут рассматривать любое использование операторов реляционных или вычитаний на двух указателях в качестве обещания программиста, что указанные указатели всегда будут идентифицировать один и тот же живой объект и опустить любой код, который был бы релевантным только в том случае, если это предположение не было. Следовательно, несмотря на то, что многие аппаратные платформы могут предлагать гарантии, которые были бы полезны для многих алгоритмов, нет безопасного способа, с помощью которого код может использовать любые такие гарантии, даже если код никогда не понадобится запускать на аппаратном обеспечении, которое, естественно, не обеспечивает их.

Ответ 3

Указатели содержат адреса переменных, которые они ссылаются. Адреса действительны, даже если переменные, которые раньше были сохранены, были выпущены/уничтожены/недоступны. Если вы не пытаетесь использовать значения на этих адресах, вы в безопасности, то значения * p и * q будут undefined.

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

Является ли это осмысленной практикой, совершенно другое обсуждение.