Почему в C ++ 11 есть странное предложение о сравнении недействительных указателей?

Проверяя ссылки для другого вопроса, я заметил странное предложение в C ++ 11 по адресу [expr.rel] ¶3:

Указатели на void (после преобразований указателей) можно сравнивать с результатом, определяемым следующим образом: если оба указателя представляют один и тот же адрес или оба являются значениями нулевого указателя, результатом будет true, если оператор <= или >=, и false в противном случае; в противном случае результат не указан.

Похоже, это означает, что после того, как два указателя были преобразованы в void *, их отношение упорядочения больше не гарантируется; например, это:

int foo[] = {1, 2, 3, 4, 5};
void *a = &foo[0];
void *b = &foo[1];
std::cout<<(a < b);

казалось бы, не указано.

Интересно, что этого предложения не было в C ++ 03 и исчезло в C ++ 14, поэтому, если мы возьмем приведенный выше пример и применим к нему формулировку C ++ 14, я бы сказал, что ¶3.1

  • Если два указателя указывают на разные элементы одного и того же массива или на его подобъекты, указатель на элемент с более высоким нижним индексом сравнивается с большим.

будет применяться, поскольку a и b указывают на элементы одного и того же массива, даже если они были преобразованы в void *. Обратите внимание, что формулировка ¶3.1 была почти такой же в C ++ 11, но, похоже, была отменена предложением void *.

Прав ли я в своем понимании? Какой смысл в этом странном предложении, добавленном в C ++ 11 и немедленно удаленном? Или, может быть, он все еще там, но перемещен в / подразумевается какой-то другой частью стандарта?


person Matteo Italia    schedule 25.01.2019    source источник
comment
Это не странно, C и C ++ являются типизированными языками, присвоение адреса целочисленного массива void * не похоже на подобное присвоение, поэтому требуется приведение. static_cast ‹void *› (foo);   -  person SPlatten    schedule 25.01.2019
comment
@SPlatten: любой указатель данных имеет неявное преобразование в void *, поэтому явное преобразование не требуется, хотя верно, что на некоторых нечетных архитектурах (на ум приходит сегментированная память) преобразование в void * может не быть простой побитовой копией; тем не менее, я не могу представить архитектуру, в которой обычный указатель на преобразование большого void указателя не сохранил бы отношения упорядочения между элементами одного и того же массива.   -  person Matteo Italia    schedule 25.01.2019
comment
Я мог бы представить сравнение указателя, которое реализуется как подсчет количества элементов между a и b; если отрицательно, то b ‹a. Конечно, это не сработает для void*, поскольку void[] нет.   -  person MSalters    schedule 25.01.2019
comment
@MSalters: Я также могу представить, что a - b будет реализован как int c; while((c = rand()) + b == a);, но это не значит, что это хоть сколько-нибудь разумно. :-) Кроме того, я ожидал, что это нарушит требования временной сложности, указанные где-то в разделе алгоритмов / контейнеров.   -  person Matteo Italia    schedule 25.01.2019
comment
Согласно формулировке этого пункта char c; bool b = (void *)&c == (void *)&c было бы ложным вопреки всем причинам. Я правильно читаю?   -  person Peter - Reinstate Monica    schedule 25.01.2019
comment
@ PeterA.Schneider: Я считаю, что вы читаете это неправильно, здесь стандарт говорит только о реляционных операторах (<, <=, >=, >), операторы равенства обсуждаются в следующем разделе ([expr.eq]), что гарантирует что результат вашего выражения действительно true (Два указателя одного типа сравниваются равными тогда и только тогда, когда они оба равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес (3.9.2). < / я>)   -  person Matteo Italia    schedule 25.01.2019
comment
@MatteoItalia Ах, спасибо. Здесь нет стандарта.   -  person Peter - Reinstate Monica    schedule 25.01.2019
comment
Правильно, стандарт заключается в том, что x<=y на voids имеет семантику x==y||x<y; если левая часть верна, то результат определенно верен и четко определен. Если левая часть ложна, значит, мы находимся на территории неопределенного поведения; нет никакой гарантии, что указатели void упорядочены каким-либо согласованным образом. Я считаю, что это еще одна ошибка; разработчик, который пишет <= явно не имеет в голове ни равенства, ни УБ.   -  person Eric Lippert    schedule 25.01.2019


Ответы (1)


TL; DR:

  • в C ++ 98/03 предложение отсутствовало, и стандарт не определял реляционные операторы для указателей void (основная проблема 879, см. конец этого сообщения);
  • странное предложение о сравнении указателей void было добавлено в C ++ 11 для его решения, но это, в свою очередь, привело к двум другим основным проблемам 583 и 1512 (см. ниже);
  • решение этих проблем потребовало удаления этого пункта и замены его формулировкой из стандарта C ++ 14, допускающей «нормальное» void * сравнение.

Основной вопрос 583: Сравнение реляционного указателя с константой нулевого указателя < / а>

  1. Сравнение реляционного указателя с константой нулевого указателя Раздел: 8.9 [expr.rel]

В C это неправильно сформировано (см. C99 6.5.8):

void f(char* s) {
    if (s < 0) { }
} ...but in C++, it's not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?

Это было в языке со времен ARM (и, возможно, раньше); очевидно, это связано с тем, что преобразования указателя (7.11 [conv.ptr]) должны выполняться для обоих операндов всякий раз, когда один из операндов имеет тип указателя. Таким образом, похоже, что преобразование типа «нулевой указатель в реальный указатель» увязло с другими преобразованиями указателя.

Предлагаемое решение (апрель 2013 г.):

Эта проблема устранена путем решения проблемы 1512.

Основной вопрос 1512: Сравнение указателя и преобразование квалификаций

  1. Сравнение указателей и преобразование квалификаций Раздел: 8.9 [expr.rel]

Согласно 8.9 [expr.rel] параграф 2, описывающий сравнение указателей,

Преобразования указателя (7.11 [conv.ptr]) и квалификационные преобразования (7.5 [conv.qual]) выполняются для операндов-указателей (или для операнда-указателя и константы нулевого указателя, или для двух констант нулевого указателя, по крайней мере одна из которых не является целым), чтобы привести их к их типу составного указателя. Это может сделать следующий пример некорректным:

 bool foo(int** x, const int** y) {
 return x < y;  // valid ?   } because int** cannot be converted to const int**, according to the rules of 7.5 [conv.qual] paragraph 4.

Это кажется слишком строгим для сравнения указателей, и текущие реализации принимают этот пример.

Предлагаемое решение (ноябрь 2012 г.):


Соответствующие выдержки из решения вышеуказанных проблем можно найти в документе: Сравнение указателя и преобразование квалификаций (версия 3).

Следующие действия также решают основную проблему 583.

Изменение в 5.9 expr.rel, абзацы с 1 по 5:

В этом разделе следующий оператор (нечетное предложение в C ++ 11) был удален:

Указатели на void (после преобразований указателей) можно сравнивать с результатом, определяемым следующим образом: если оба указателя представляют один и тот же адрес или оба являются значениями нулевого указателя, результатом будет true, если оператор <= или >=, и false в противном случае; в противном случае результат не указан

И следующие утверждения были добавлены:

  • Если два указателя указывают на разные элементы одного и того же массива или на его подобъекты, указатель на элемент с более высоким нижним индексом сравнивается с большим.
  • Если один указатель указывает на элемент массива или на его подобъект, а другой указатель указывает на один за последним элементом массива, последний указатель сравнивается больше.

Итак, в окончательном рабочем проекте раздела [expr.rel] / 3 C ++ 14 (n4140) вышеупомянутые утверждения находятся в том виде, в каком они были заявлены во время разрешения.


Поиски причины, по которой было добавлено это странное предложение, привело меня к гораздо более ранней проблеме 879: Отсутствуют встроенные операторы сравнения для типов указателей. Предлагаемое решение этого вопроса (в июле 2009 г.) привело к добавлению этого пункта, который был принят WP в октябре 2009 г.

Так он был включен в стандарт C ++ 11.

person P.W    schedule 25.01.2019
comment
Это объясняет, почему он был удален, но почему он был добавлен в первую очередь? Чего комитет пытался достичь, ограничивая сравнение void *? Или я ошибаюсь, и пример в моем вопросе приводит к неопределенному поведению на текущем C ++ (IOW они просто изменили формулировку)? - person Matteo Italia; 25.01.2019
comment
@MatteoItalia: Я попытался выяснить причину, по которой он был добавлен, поискав раздел «Совместимость с C ++ 03» в стандарте C ++ 11, но не смог найти ничего связанного с этим. Текущий C ++ (17) также не упоминает это предложение в какой-либо форме (насколько я мог проверить стандарт), поэтому поведение может быть просто неопределенным. - person P.W; 25.01.2019
comment
Оговорка о странностях не имела отношения ни к одному из основных вопросов. Это скорее временное исправление, поскольку в решении проблемы был переписан подпункт. - person T.C.; 25.01.2019
comment
@MatteoItalia: см. Отредактированный ответ, который отвечает на вопрос, почему вообще было добавлено это странное предложение. - person P.W; 28.01.2019
comment
@ P.W: ааа, это интересно! Итак, раньше этого предложения не было, но предыдущая формулировка просто полностью void * исключила из разрешенных типов для реляционных операторов. C ++ 11 добавил к этому первый патч, чтобы по крайней мере сравнения для <= и >= были согласованы с оператором ==, а C ++ 14 увидел, что это различие было больше проблем, чем оно того стоило, и предоставил все реляционные операторы регулярная семантика даже для недействительных указателей. Я правильно это читаю? - person Matteo Italia; 29.01.2019
comment
@MatteoItalia: Хотя, как правило, сложно спекулировать на мотивах добавления / удаления патчей к стандарту, в данном случае я думаю, что вы правы. С введением оператора <=> в C ++ 20 мне интересно, как будет определяться семантика по отношению к указателям. - person P.W; 29.01.2019
comment
@ P.W: отличная детективная работа! Когда у вас будет минутка, я думаю, что было бы лучше добавить эти новые результаты в таблицу вверху, тогда я буду рад принять ваш ответ! - person Matteo Italia; 29.01.2019
comment
Тем не менее, я все еще немного сомневаюсь ... новая формулировка определяет реляционные операторы только для указателей на объекты (сравнение указателей на объекты определяется следующим образом C ++ 14, [expr.rel], ¶3), но они void * считали указатели на объекты? Что-то есть в этом на [basic.compound] ¶3, но я не могу в этом разобраться. - person Matteo Italia; 29.01.2019
comment
Тип указателя на void или указателя на тип объекта называется типом указателя объекта. [Примечание: указатель на void не имеет типа указателя на объект, потому что void не является типом объекта. - конец примечания] Тип указателя, который может обозначать функцию, называется типом указателя функции. Указатель на объекты типа T называется «указателем на T. » [Пример: указатель на объект типа int называется «указателем на int», а указатель на объект класса X называется «указателем на X». - конец примера] О чем говорится в записке? - person Matteo Italia; 29.01.2019
comment
Примечание, которое должно было сделать предыдущее заявление более ясным, похоже, сделало его более запутанным. Я хотел узнать, что компилятор думает о void*, и использовал is_pointer на void* и он оценивается как true. coliru.stacked-crooked.com/a/b2cfff1ba17336e9. - person P.W; 29.01.2019
comment
is_pointer проверяет, является ли T указателем на объект или указателем на функцию (но не указателем на член / функцию-член), и предоставляет значение константы члена, равное true, если T является объектом / тип указателя функции. В противном случае значение равно false. - person P.W; 29.01.2019
comment
@MatteoItalia: обновлен раздел TL; DR. - person P.W; 29.01.2019
comment
@ P.W: Я переписал раздел TL; DR, чтобы шаги были более ясными и точными; не стесняйтесь редактировать / возвращать его, если он вам не нравится. - person Matteo Italia; 30.01.2019
comment
@MatteoItalia: Теперь поток выглядит более структурированным. Спасибо за редактирование. - person P.W; 30.01.2019