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

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

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

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


person fredoverflow    schedule 07.06.2015    source источник
comment
Дайте определение юридическим.   -  person rightfold    schedule 07.06.2015
comment
По крайней мере, не неопределенное поведение.   -  person Puppy    schedule 07.06.2015
comment
@rightfold Рискую ли я получить отказ от языкового юриста?   -  person fredoverflow    schedule 07.06.2015
comment
Некоторое значение: stackoverflow.com/questions/17024866/   -  person Oliver Charlesworth    schedule 07.06.2015
comment
@OliverCharlesworth ergh, смешанный вопрос C и C ++ ... эти два языка имеют значительно разные правила в этой области. Стандарт C недвусмысленно говорит, что p и q здесь неопределенные.   -  person M.M    schedule 07.06.2015
comment
В качестве точки данных gcc оптимизирует int*f(){int a;return &a;} до return 0;.   -  person Marc Glisse    schedule 07.06.2015
comment
Это своего рода должно стать двумя частями... (1) допустимо ли использовать висячий указатель для стекового объекта, и (2) если да, то каков результат сравнения. Я попытался обратиться к обоим в своем ответе   -  person M.M    schedule 07.06.2015
comment
Я хотел бы знать, какой смысл это делать   -  person Ed Heal    schedule 07.06.2015
comment
@EdHeal: в строгости есть ценность. Пройдите любой курс формальной семантики, чтобы узнать почему.   -  person Lightness Races in Orbit    schedule 07.06.2015
comment
@LightnessRacesinOrbit - как это прагматично? Что касается формальной семантики, то это была головная боль, которую я давно потерял после окончания магистратуры.   -  person Ed Heal    schedule 07.06.2015
comment
@EdHeal: Как я уже сказал, если вы хотите узнать практический результат тщательного изучения формальной семантики, [повторно] пройдите курс по ней. Ответ на этот вопрос путь выходит за рамки этой ветки комментариев. Тег language-laywer существует для вопросов в этом домене. Я не говорю, что все должны делать это, чтобы просто создавать компьютерные программы, но тогда не каждый вопрос нужно помечать тегом language. -юрист, и ваш вывод о том, что в этом нет никакой ценности, недальновиден.   -  person Lightness Races in Orbit    schedule 07.06.2015
comment
@LightnessRacesinOrbit - я просто не могу придумать для этого никакого применения - и хочу быть просветленным для любого использования   -  person Ed Heal    schedule 07.06.2015
comment
@EdHeal Я полагаю, в этом конкретном случае вы могли бы определить, выполнял ли компилятор некоторую оптимизацию   -  person M.M    schedule 09.06.2015
comment
Указатель может легально указывать куда угодно, если ему может быть присвоено значение NULL. О чем вам следует беспокоиться, чтобы быть незаконным, так это о том, чтобы прочитать его содержимое.   -  person hamidi    schedule 23.02.2020


Ответы (3)


Введение. Первый вопрос заключается в том, разрешено ли вообще использовать значение 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 как отражающую цель стандарта в данном случае.


person M.M    schedule 07.06.2015
comment
Показать стандартные котировки. - person Puppy; 07.06.2015
comment
Как печать значения указателя менее законна, чем сравнение его на равенство? - person fredoverflow; 07.06.2015
comment
@fredoverflow одинаково законно (то есть: нет) - person M.M; 07.06.2015
comment
@MattMcNabb Меня очень интересует обоснование вашего ответа, потому что, насколько я знаю, то, что вы заявили, не соответствует действительности. - person Filip Roséen - refp; 07.06.2015
comment
Значение в указателе не является недопустимым, см. [basic.compound]p3. - person Filip Roséen - refp; 07.06.2015
comment
@FilipRoséen-refp, можете ли вы процитировать, какая часть этого раздела актуальна? ничего особенного не вижу - person M.M; 07.06.2015
comment
@Filip: Действительное значение типа указателя объекта представляет либо адрес байта в памяти (1.7), либо нулевой указатель (4.10). Я понимаю, почему вы хотите использовать это для сказать, что это не недопустимые указатели, но они очевидно таковыми являются. Я думаю, что это дефект формулировки, потому что предложение, которое я только что процитировал, говорит нам, какие значения физически способен удерживать указатель, в отличие от понятия достоверности, которое описывает, действительно ли он указывает на объект (и совершенно очевидно, что правила Мэтта кавычки должны применяться к висячим указателям, к чему еще они могут применяться?!). - person Lightness Races in Orbit; 07.06.2015
comment
@LightnessRacesinOrbit Сравнение двух указателей не требует, чтобы они указывали на живой объект, это описано в [expr.eq]p1: Указатели одного типа (после преобразования указателей) можно сравнивать на равенство. Два указателя одного типа считаются равными тогда и только тогда, когда они оба равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес (3.9.2). - person Filip Roséen - refp; 07.06.2015
comment
@LightnessRacesinOrbit адрес байта в памяти кажется немного расплывчатым, это дерьмовое определение - person M.M; 07.06.2015
comment
Указатели @FilipRoséen-refp нельзя сравнивать до тех пор, пока не будет выполнено преобразование lvalue-to-rvalue для значений gl, заданных в качестве аргумента, и именно это преобразование вызывает UB (или определяется реализацией) - person M.M; 07.06.2015
comment
@MattMcNabb: Действительно. Это не только утечка абстракции, но и противоречит другой формулировке, требующей, чтобы указатели указывали на объекты. - person Lightness Races in Orbit; 07.06.2015
comment
Стандартные цитаты @Puppy включены - person M.M; 07.06.2015
comment
@LightnessRacesinOrbit это p2 в N3797, я (по ошибке) смотрел на N3337. @MattMcNabb преобразование lvalue-to-rvalue не является проблемой, поскольку значения указателей не являются недействительными. Цитата, связанная с недопустимыми значениями указателя и неопределенным поведением, актуальна для таких случаев, как int * p = reinterpret_cast<int*> (0xDEADBEEF); // might trap. - person Filip Roséen - refp; 07.06.2015
comment
@FilipRoseen: Согласно вашему аргументу, это не может быть ловушкой, поскольку память по адресу 0xDEADBEEF на самом деле содержит байт, а 0xDEADBEEF — это адрес. - person Puppy; 07.06.2015
comment
@FilipRoséen-refp: Пожалуйста, указывайте стандарт, а не черновик. - person Lightness Races in Orbit; 07.06.2015
comment
@FilipRoséen-refp, каков ваш аргумент в пользу того, что указатели недействительны? 3.7.4.2/4 показывает, что от того, куда указывает указатель, зависит, является ли он допустимым или нет. - person M.M; 07.06.2015
comment
@Puppy Нет, потому что адрес не получается путем получения адреса объекта - поэтому ничто не говорит о том, что это допустимое местоположение для указателя на целое. - person Filip Roséen - refp; 07.06.2015
comment
возможно, для этого нам нужен отчет о дефекте, я не вижу причин, по которым освобожденные объекты стека должны иметь другой статус, чем освобожденные объекты кучи. - person M.M; 07.06.2015
comment
@LightnessRacesinOrbit в настоящее время находится в пути, в настоящее время у меня нет доступа к официальному выпуску (но если это обсуждение продолжит расти, я процитирую соответствующий документ - и, возможно, напишу свой собственный ответ - позже сегодня вечером. У меня есть ужин, чтобы присутствовать за 30 минут, просто убивая время в универе). - person Filip Roséen - refp; 07.06.2015
comment
@LightnessRacesinOrbit Пожалуйста, купите мне копию стандарта, чтобы я мог это сделать (было бы здорово, если бы вы отправили мне печатную копию по почте, чтобы я мог показать фактический стандарт в своих ответах, а не только его содержание, что, по-видимому, не имеет к вам никакого отношения (я имею в виду содержание)). Кстати, Филип говорит, что его также заинтересовала бы печатная копия. - person Griwes; 07.06.2015
comment
о достоверности указателя также говорят в контексте контейнера, некоторые операции делают указатели недействительными, и они могут быть связаны с пространством стека (например, std::string с использованием SSO) - person M.M; 07.06.2015
comment
Остальные из нас не покупают Стандарт. Мы цитируем новейший свободно доступный проект, обычно FDIS или около того, но формулировка таких вопросов не сильно меняется. - person Puppy; 07.06.2015
comment
@LightnessRacesinOrbit Если вы знаете разницу между документом Nxxxx, FDIS и официальным стандартом, то вам следует распознать N-число, соответствующее наиболее близкому приближению к официальному стандарту, который общедоступен в Интернете бесплатно. Нелепо ожидать, что люди потратят несколько сотен долларов только для того, чтобы иметь немного больше убедительной силы в том, что равнозначно спорному спору. - person zwol; 07.06.2015
comment
@zwol: на самом деле вполне разумно установить любой барьер для входа, чтобы сбить кого-то в том, что равносильно спору о споре. Суть в том, чтобы победить, а не быть правым ;-) Если бы смысл был в правильном ответе, то, конечно, Лайтнесс мог бы сказать ... и опубликованный стандарт тот же/другой, а не попытка дискредитировать цитату без его замены. Я имею в виду, я думаю, что Лайтнесс прав, но проблема с цитатами Филипа в том, что они не подтверждают его утверждения, а не в том, что они неточны. - person Steve Jessop; 07.06.2015
comment
@zwol: Смешно ожидать, что кто-то поверит на слово стандартной цитате, которая не соответствует стандарту. Что касается именования черновика, то автор, а не читатель, несет ответственность за выполнение соответствующего перевода с Nxxxx на C++yy FDIS. Конечно, Стив прав в том, что я лишь делаю побочный запрос; Я не утверждаю, что неспособность Филипа процитировать стандарт как-то связана с его неправотой: это совершенно другой вопрос. :П - person Lightness Races in Orbit; 07.06.2015
comment
@Puppy: Под остальными вы подразумеваете вас и нескольких избранных ваших интернет-друзей. - person Lightness Races in Orbit; 07.06.2015
comment
@LightnessRacesinOrbit Лично я вполне согласен с кавычками N3936, если только кто-то с копией C ++ 14 специально не вмешается и не укажет на разницу (которой, насколько мне известно, нет). То же самое касается С++ 11 и N3337. - person M.M; 07.06.2015
comment
@LightnessRacesInOrbit: Боюсь, что без статистического исследования невозможно продемонстрировать, что люди покупают Стандарт, чтобы цитировать его в случайных интернет-дискуссиях. - person Puppy; 07.06.2015
comment
@LightnessRacesinOrbit: Слушай, просто сдайся и купи печатную копию стандарта для всех с тегом C++ в stackoverflow. Тогда вам больше никогда не придется запрашивать цитату из стандарта. Гораздо быстрее и эффективнее, чем говорить об этом. - person SigTerm; 07.06.2015
comment
@Lightness: я просто просматриваю исходный комментарий, который вы сделали, который теперь удален, в котором вы используете остальных точно так же, как и я. Я просто нахожу это лицемерным с вашей стороны. - person Puppy; 07.06.2015
comment
Я не согласен с последней строкой, что это также может быть неопределенное поведение. - person haccks; 07.06.2015
comment
@hackks можешь уточнить? Реализация может определить что-то вроде того, что значение указателя не указано, или представление значения остается неизменным, или что-то еще, а затем оператор == может или не может обнаружить, что они оба дают один и тот же байт. Я наблюдал true и false с g++ -std=c++14 в зависимости от настроек оптимизации. - person M.M; 08.06.2015
comment
@МэттМакНабб; Оба указателя могут указывать на одно и то же место, а могут и не указывать. Результат будет либо true, либо false. Я не понимаю, почему значение указателя не указано? - person haccks; 08.06.2015
comment
@hackks Это результат p == q, который, как я говорю, будет неуказанным. (Это был оригинальный вопрос ОП) - person M.M; 08.06.2015
comment
@МэттМакНабб; Я говорил о вашем последнем комментарии. Я имею в виду, что сравнение двух указателей, которые не инициализированы каким-либо адресом, может указывать или не указывать на один и тот же адрес. Но гарантировано, что они будут указывать на какое-то место в памяти (случайное). В этом случае результат оператора == не будет неопределенным. - person haccks; 08.06.2015
comment
@hackks мой ответ - это ответ на вопрос OP о сравнении двух указателей, указывающих на объекты, после того, как объекты были освобождены. Это отличается от неинициализированного указателя. Неинициализированные указатели содержат неопределенные значения, и попытка сравнить два неопределенных значения вызывает UB (N3936 [dcl.init]/12) - person M.M; 08.06.2015
comment
N4430, в случае принятия, будет решить недействительный вопрос. - person T.C.; 08.06.2015
comment
Можно ли вместо этого вернуться и сравнить uintptr_t? - person Managarm; 10.06.2016

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

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

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

person supercat    schedule 08.06.2015

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

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

Является ли это значимой практикой — это совершенно другой вопрос.

person user2038893    schedule 10.06.2015
comment
Это не просто законно, это определяется реализацией. - person Mark Hurd; 12.06.2015
comment
Я согласен, что результат (p == q) определяется реализацией. - person user2038893; 15.06.2015