Является ли неуказанным поведением сравнение указателей на разные массивы на предмет равенства?

Операторы равенства имеют семантические ограничения операторов отношения для указателей:

Операторы == (равно) и != (не равно) имеют те же семантические ограничения, преобразования и тип результата, что и операторы отношения, за исключением их более низкого приоритета и истинностного результата. [С++ 03 §5.10p2]

А реляционные операторы имеют ограничение на сравнение указателей:

Если два указателя p и q одного и того же типа указывают на разные объекты, которые не являются членами одного и того же объекта или элементами одного и того же массива, или на разные функции, или если только один из них равен нулю, результаты p‹q, p >q, p‹=q и p>=q не указаны. [§5.9p2]

Является ли это семантическим ограничением, которое «наследуется» операторами равенства?

В частности, учитывая:

int a[42];
int b[42];

Ясно, что (a + 3) ‹ (b + 3) не указано, но является ли (a + 3) == (b + 3) также неопределенным?


person Fred Nurk    schedule 05.02.2011    source источник
comment
Интересный вопрос. Если бы это было так, как быть со всеми тестами на самоназначение if (this != &other)   -  person UncleBens    schedule 06.02.2011
comment
За исключением непрозрачной формулировки, все кажется довольно простым: стандарт полностью определяет, при каких обстоятельствах два указателя сравниваются не на равных. Однако какое значение указателя (адреса) двух несвязанных объектов больше, просто (и - ИМХО - довольно очевидно) не указано.   -  person Martin Ba    schedule 06.02.2011
comment
@Martin: Как насчет сегментированной архитектуры с близкими указателями, имеющими одинаковое смещение, но для разных сегментов? Я не думаю, что вы хотели бы, чтобы равенство было полностью указано в этом случае, и стандарт требует, чтобы этот случай сравнения был правильно сформирован (должен компилироваться, выполняться и т. д.), насколько я могу судить.   -  person Fred Nurk    schedule 06.02.2011
comment
стандарт требует осмысленных результатов даже в этом случае, т. е. == должно быть истинным только в том случае, если оба они являются нулевыми указателями или ссылаются на один и тот же объект (и обратное для !=, конечно).   -  person Jerry Coffin    schedule 06.02.2011
comment
В сегментированной памяти операторы == и != также должны сравнивать сегментную часть указателя.   -  person Goswin von Brederlow    schedule 25.10.2019


Ответы (3)


Семантика для op== и op!= явно говорит, что сопоставление за исключением их истинностного результата. Поэтому вам нужно посмотреть, что определено для их истинностного результата. Если говорят, что результат неконкретный, значит, он неконкретный. Если они определяют конкретные правила, то это не так. Это говорит, в частности

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

person Johannes Schaub - litb    schedule 05.02.2011
comment
Я не читал это как неопределенность результата, за исключением их истинностного результата, поскольку это, казалось бы, отрицает те же семантические ограничения. Я не уверен, что этот ответ - лучший способ интерпретировать или нет, но он решит этот вопрос. - person Fred Nurk; 06.02.2011
comment
+1: я полностью согласен с интерпретацией за исключением их истинностного результата. Хотя это ужасно стандартный язык :-) - person Martin Ba; 06.02.2011
comment
Цитируемый текст был заменен DR1652. - person M.M; 08.02.2018

Результат операторов равенства (== и !=) дает указанные результаты, если указатели относятся к объектам одного и того же типа. При наличии двух указателей на один и тот же тип верно только одно из следующего:

  1. оба являются нулевыми указателями, и они равны друг другу.
  2. оба являются указателями на один и тот же объект, и они равны друг другу.
  3. они являются указателями на разные объекты, и они не равны друг другу.
  4. по крайней мере один из них не инициализирован, а результат сравнения не определен (и, по сути, само сравнение может никогда не произойти — просто попытка прочитать указатель для выполнения сравнения приводит к неопределенному поведению).

При тех же ограничениях (оба указателя относятся к одному и тому же типу объекта) результат операторов упорядочивания (<, <=, >, >=) указывается только в том случае, если оба они являются указателями на один и тот же объект или на отдельные объекты в тот же массив (и для этой цели «кусок» памяти, выделенный с помощью malloc, new и т. д., квалифицируется как массив). Если указатели ссылаются на отдельные объекты, которые не являются частью одного и того же массива, результат не указан. Если один или оба указателя не инициализированы, у вас неопределенное поведение.

Однако, несмотря на это, шаблоны сравнения в стандартной библиотеке (std::less, std::greater, std::less_equal и std::greater_equal) делают все дают осмысленный результат, даже если встроенные операторы этого не делают. В частности, они необходимы для получения полного порядка. Таким образом, вы можете получить порядок, если хотите, но не со встроенными операторами сравнения (хотя, конечно, если один или оба указателя не инициализированы, поведение все еще не определено).

person Jerry Coffin    schedule 05.02.2011
comment
Реляционные операторы определены, но не указаны в данном случае. Ни один из операторов сравнения (при использовании с указателями) не определен. Я спрашиваю о ==, а не о ‹, и std::equal_to говорит, что использует ==, не будучи включенным в специальное разрешение для std::less и т. д. - person Fred Nurk; 06.02.2011
comment
@Fred - операторы определены, а результаты - нет. Думаю, я мог бы попытаться переформулировать это, чтобы сделать его более ясным, но (IMO) то, что я сказал, уже легче понять, чем формулировку в стандарте. - person Jerry Coffin; 06.02.2011
comment
Результаты не указаны, что явно отличается от неопределенного (как в неопределенном поведении). Терминологическая придирка, возможно, но важная, поскольку UB имеет серьезные последствия; и один, который я нашел удивительным, так как я также думал, что они были UB. - person Fred Nurk; 06.02.2011
comment
По крайней мере, IMO, сказать, что результат не определен, отличается от того, что код имеет неопределенное поведение, но я немного отредактировал, чтобы избежать этого недоразумения. - person Jerry Coffin; 06.02.2011
comment
Я думаю, что вы используете результат, не определенный, чтобы означать именно то, что стандарт означает с неуказанным, который, в отличие от определенного реализацией, не требует документации и, следовательно, не требует согласованности. (Я основываю отсутствие согласованности на недокументированном поведении, которое всегда может включать неочевидные и неуказанные факторы, влияющие на него.) Мы оба определенно согласны с тем, что это отличается от неопределенного, но если вы имеете в виду, что стандарт делает с неопределенным, то я думаю, что это лучше использовать это слово, говоря на стандартном языке. - person Fred Nurk; 06.02.2011
comment
Я хотел бы отметить, что для C сравнение несвязанных указателей приводит к неопределенному поведению (6.5.8.5). - person Blaisorblade; 13.04.2011
comment
В вашем случае 3 есть особый случай, о котором следует упомянуть: сравнение указателя одного объекта, следующего за конечным, с начальным указателем другого объекта. В исходном тексте С++ 11 на самом деле говорилось, что этот случай должен сравниваться равным; однако DR 1652 изменил/уточнил его на быть неуказанным - person M.M; 08.02.2018

Поскольку существует путаница в семантике соответствия, это правила для C++. C использует совершенно другую модель соответствия.

  1. Неопределенное поведение — это оксюморон, означающий, что переводчик НЕ ваша программа может делать все, что ему заблагорассудится. Обычно это означает, что он может генерировать код, который также будет делать все, что ему заблагорассудится (но это вывод). Там, где в Стандарте указано, что поведение не определено, текст на самом деле не имеет значения для пользователя в том смысле, что исключение этого текста не изменит требования, предъявляемые Стандартом к переводчикам.

  2. Некорректная программа означает, что, если не указано иное, поведение транслятора жестко определено: требуется отклонить вашу программу и выдать диагностическое сообщение. Основным особым случаем здесь является правило одного определения, если вы нарушаете, что ваша программа плохо сформирована, но диагностика не требуется.

  3. Определенная реализация налагает на переводчика требование, чтобы он содержал документацию, явно определяющую поведение. В этом особом случае результатом может быть Undefined Behavior, но это должно быть указано явно.

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

В общем, стандарт C++ — это не стандарт языка, это модель для стандарта языка. Чтобы сгенерировать фактический стандарт, вы должны подключить различные параметры. Легче всего распознать ограничения, определенные реализацией.

В стандарте есть пара глупых конфликтов, например, законный транслятор может отклонить любую, казалось бы, хорошую программу на C++ на том основании, что вы должны предоставить функцию main(), но транслятор поддерживает только идентификаторы из 1 символа. Эта проблема решается с помощью понятия QOI или качества реализации. По сути, это говорит о том, что никто не собирается покупать этот компилятор только потому, что он соответствует требованиям.

Технически неопределенный характер operator <, когда указатели относятся к несвязанным объектам, вероятно, означает: вы получите какой-то результат, который является либо истинным, либо ложным, но ваша программа не рухнет, однако это не правильное значение неуказанного, поэтому это Дефект: не указано наложило бремя на разработчиков стандартов по документированию набора разрешенных вариантов поведения, потому что, если набор открыт, то это эквивалентно неопределенному поведению.

На самом деле я предложил std::less как решение проблемы, заключающейся в том, что некоторые структуры данных требуют полного упорядочения ключей, а указатели не полностью упорядочены operator <. На большинстве машин, использующих линейную адресацию, less совпадает с <, но операция less, скажем, на процессоре x86 потенциально дороже.

person Yttrill    schedule 06.02.2011
comment
Какой термин вы бы предпочли для описания ситуации, когда нет никаких гарантий относительно значения, которое выдаст выражение, но код, тем не менее, может законно вычислить выражение, если он подготовлен для любого значения, которое может получиться? Например, предположим, что f(x), f(y) и f(z) в идеале должны давать одно и то же значение, но не более одного из x, y или z может быть искажено. Если операции над поврежденным значением дают неопределенный результат, можно (при отсутствии побочных эффектов) безопасно сказать temp = f(x); return temp==f(y) ? temp : f(z);, даже если невозможно определить, были ли повреждены x, y или z. - person supercat; 11.12.2013
comment
Если бы вызов f() для поврежденного значения не был гарантирован безопасным, то, если бы кто-то не сохранил какие-либо другие средства определения того, были ли повреждены x, y или z, невозможно было бы справиться с повреждением. С другой стороны, если вызов безопасен, то даже если при сохранении temp нельзя было бы знать, является ли значение хорошим или нет, тем не менее, можно было бы определить это позже. - person supercat; 11.12.2013
comment
Реализация не обязана отвергать неправильно сформированную программу. Он должен генерировать диагностику, но может продолжать генерировать двоичный файл или делать что-то еще. - person M.M; 08.02.2018