Законно ли сравнивать висячие указатели?
int *p, *q;
{
int a;
p = &a;
}
{
int b;
q = &b;
}
std::cout << (p == q) << '\n';
Обратите внимание, как p
, так и q
указывают на объекты, которые уже исчезли. Это законно?
Законно ли сравнивать висячие указатели?
int *p, *q;
{
int a;
p = &a;
}
{
int b;
q = &b;
}
std::cout << (p == q) << '\n';
Обратите внимание, как p
, так и q
указывают на объекты, которые уже исчезли. Это законно?
Введение. Первый вопрос заключается в том, разрешено ли вообще использовать значение p
.
После уничтожения a
p
получает так называемое недопустимое значение указателя. Цитата из N4430 (для обсуждения статуса N4430 см. " Примечание» ниже):
Когда достигается конец продолжительности области хранения, значения всех указателей, представляющих адрес любой части освобожденного хранилища, становятся недействительными значениями указателя.
Поведение при использовании недопустимого значения указателя также описано в том же разделе N4430 (и почти идентичный текст появляется в C++14 [basic.stc.dynamic.deallocation]/4):
Косвенное обращение через недопустимое значение указателя и передача недопустимого значения указателя в функцию освобождения имеют неопределенное поведение. Любое другое использование недопустимого значения указателя имеет поведение, определяемое реализацией.
[ Сноска: В некоторых реализациях может быть определено, что копирование недопустимого значения указателя вызывает сгенерированную системой ошибку среды выполнения. — конец сноски]
Поэтому вам нужно будет обратиться к документации по вашей реализации, чтобы узнать, что здесь должно произойти (начиная с С++ 14).
Термин использовать в приведенных выше цитатах означает требующее преобразования lvalue-to-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 как отражающую цель стандарта в данном случае.
[basic.compound]p3
.
- person Filip Roséen - refp; 07.06.2015
[expr.eq]p1
: Указатели одного типа (после преобразования указателей) можно сравнивать на равенство. Два указателя одного типа считаются равными тогда и только тогда, когда они оба равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес (3.9.2).
- person Filip Roséen - refp; 07.06.2015
p2
в N3797, я (по ошибке) смотрел на N3337. @MattMcNabb преобразование lvalue-to-rvalue не является проблемой, поскольку значения указателей не являются недействительными. Цитата, связанная с недопустимыми значениями указателя и неопределенным поведением, актуальна для таких случаев, как int * p = reinterpret_cast<int*> (0xDEADBEEF); // might trap
.
- person Filip Roséen - refp; 07.06.2015
std::string
с использованием SSO)
- person M.M; 07.06.2015
==
может или не может обнаружить, что они оба дают один и тот же байт. Я наблюдал true
и false
с g++ -std=c++14
в зависимости от настроек оптимизации.
- person M.M; 08.06.2015
true
, либо false
. Я не понимаю, почему значение указателя не указано?
- person haccks; 08.06.2015
p == q
, который, как я говорю, будет неуказанным. (Это был оригинальный вопрос ОП)
- person M.M; 08.06.2015
==
не будет неопределенным.
- person haccks; 08.06.2015
Исторически были некоторые системы, в которых использование указателя в качестве rvalue могло привести к тому, что система извлекла некоторую информацию, идентифицированную некоторыми битами в этом указателе. Например, если указатель может содержать адрес заголовка объекта вместе со смещением в объекте, выборка указателя может привести к тому, что система также извлечет некоторую информацию из этого заголовка. Если объект перестал существовать, попытка получить информацию из его заголовка может завершиться неудачей с произвольными последствиями.
Тем не менее, в подавляющем большинстве реализаций C все указатели, которые были активны в какой-то конкретный момент времени, всегда будут поддерживать те же отношения в отношении операторов отношения и вычитания, что и в это конкретное время. Действительно, в большинстве реализаций, если у кого-то есть char *p
, можно определить, идентифицирует ли он часть объекта, идентифицированного char *base; size_t size;
, проверив, является ли (size_t)(p-base) < size
; такое сравнение будет работать даже ретроспективно, если есть какое-либо совпадение во времени жизни объектов.
К сожалению, Стандарт не определяет способов, с помощью которых код может указать, что он требует какой-либо из последних гарантий, а также не существует стандартных средств, с помощью которых код может спросить, может ли конкретная реализация обещать какое-либо из последних поведений, и отказать в компиляции, если она не соответствует. . Кроме того, некоторые сверхсовременные реализации будут рассматривать любое использование операторов отношения или вычитания для двух указателей как обещание программиста, что рассматриваемые указатели всегда будут идентифицировать один и тот же живой объект, и опускать любой код, который был бы уместным только в том случае, если бы это предположение не удержал. Следовательно, даже несмотря на то, что многие аппаратные платформы могли бы предложить гарантии, которые были бы полезны для многих алгоритмов, не существует безопасного способа, с помощью которого код мог бы использовать любые такие гарантии, даже если коду никогда не потребуется работать на оборудовании, которое естественным образом их не предоставляет.
Указатели содержат адреса переменных, на которые они ссылаются. Адреса действительны, даже когда переменные, которые раньше там хранились, освобождены/уничтожены/недоступны. Пока вы не пытаетесь использовать значения по этим адресам, вы в безопасности, то есть *p и *q не будут определены.
Очевидно, что результат определяется реализацией, поэтому этот пример кода можно использовать для изучения возможностей вашего компилятора, если вы не хотите вникать в ассемблерный код.
Является ли это значимой практикой — это совершенно другой вопрос.
p
иq
здесь неопределенные. - person M.M   schedule 07.06.2015int*f(){int a;return &a;}
доreturn 0;
. - person Marc Glisse   schedule 07.06.2015