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

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

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

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

69

Решение

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

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

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

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

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

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

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

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

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


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


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

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

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

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


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

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

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

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

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


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


56

Другие решения

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

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

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

4

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

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

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

-3
По вопросам рекламы [email protected]