Почему С++ 11 содержит странное предложение о сравнении пустых указателей?
Проверяя ссылки на другой вопрос, я заметил странное предложение в С++ 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);
казалось бы, не определено.
Интересно, что этот пункт отсутствовал в С++ 03 и исчез в С++ 14, поэтому, если мы возьмем приведенный выше пример и применим к нему формулировку С++ 14, я бы сказал, что ¶3.1
- Если два указателя указывают на разные элементы одного и того же массива или его подобъектов, указатель на элемент с более высоким индексом сравнивается больше.
будет применяться, так как a
и b
указывают на элементы одного и того же массива, даже если они были преобразованы в void *
. Обратите внимание, что формулировка ¶3.1 была примерно такой же в С++ 11, но, похоже, была отвергнута предложением void *
.
Правильно ли я понимаю? Какой смысл в этом странном предложении, добавленном в С++ 11 и сразу удаленном? Или, может быть, он все еще там, но перенесен/подразумевается какой-то другой частью стандарта?
Ответы
Ответ 1
TL; DR:
- в С++ 98/03 этого условия не было, и стандарт не определял реляционные операторы для
void
указателей (основной вопрос 879, см. конец этого поста); - странное предложение о сравнении
void
указателей было добавлено в С++ 11 для его решения, но это, в свою очередь, привело к двум другим основным проблемам 583 и 1512 (см. ниже); - решение этих вопросов потребовало удалить пункт и заменить его формулировкой, найденной в стандарте С++ 14, которая допускает "нормальное" сравнение
void *
.
Основная проблема 583. Сравнение реляционных указателей с константой нулевого указателя
- Сравнение реляционного указателя с константой нулевого указателя Раздел: 8.9 [expr.rel]
В C это плохо сформировано (ср. C99 6.5.8):
void f(char* s) {
if (s < 0) { }
} ...but in C++, it not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?
Это было на языке начиная с ARM (и, возможно, раньше); очевидно, это потому, что преобразования указателей (7.11 [conv.ptr]) должны выполняться для обоих операндов, когда один из операндов имеет тип указателя. Таким образом, похоже, что преобразование "null-ptr-to-real-pointer-type" объединяет другие преобразования указателей.
Предлагаемое решение (апрель 2013 г.):
Эта проблема решена решением вопроса 1512.
Основная проблема 1512: сравнение указателей и преобразований квалификаций
- Сравнение указателей и преобразований квалификаций Раздел: 8.9 [expr.rel]
В соответствии с пунктом 8.9 [expr.rel], описывающим сравнения указателей,
Преобразования указателя (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:
В этом разделе было удалено следующее утверждение (нечетное предложение в С++ 11):
Указатели на void
(после преобразования указателей) можно сравнить с результатом, определенным следующим образом: если оба указателя представляют один и тот же адрес или оба являются нулевыми значениями указателя, результатом будет true
если оператор равен <=
или >=
и false
противном случае.; в противном случае результат не указан
И следующие заявления были добавлены:
- Если два указателя указывают на разные элементы одного и того же массива или его подобъектов, указатель на элемент с более высоким индексом сравнивается больше.
- Если один указатель указывает на элемент массива или его подобъекта, а другой указатель указывает один за последним элементом массива, последний указатель сравнивается больше.
Таким образом, в окончательном рабочем проекте раздела С++ 14 (n4140) [expr.rel]/3 приведенные выше утверждения находятся в том виде, в котором они были сформулированы во время принятия резолюции.
Копание по причине, по которой было добавлено это странное предложение, привело меня к гораздо более ранней проблеме 879: Отсутствие встроенных операторов сравнения для типов указателей. Предлагаемое решение этого вопроса (в июле 2009 года) привело к добавлению этого пункта, который был принят на рассмотрение WP в октябре 2009 года.
И вот как это было включено в стандарт С++ 11.
Ответ 2
Как в коммете подсказывает.
@SPlatten: любой указатель данных имеет неявное преобразование в void *, поэтому явное приведение не требуется, хотя верно, что на некоторых нечетных архитектурах (на ум приходит сегментированная память) приведение к void * не может быть простой побитовой копией; тем не менее, я не могу представить архитектуру, в которой преобразование "обычный указатель" в "большой, пустой указатель" не сохраняло бы отношение упорядочения между элементами одного и того же массива.
Ответ 3
Я хотел бы, чтобы стандарт мог гарантировать семантику сравнения на основе подписки, которую вы цитировали,
Если два указателя указывают на разные элементы одного и того же массива или его подобъектов, указатель на элемент с более высоким индексом сравнивается больше.
... но я думаю, что для выполнения этого отношения на основе индекса подписки необходимо знать тип соответствующих указателей, и для этого недостаточно void
.
Давай конкретно сосредоточимся на
... или подобъектам
Давайте возьмем "объект может содержать другие объекты, которые называются подобъектами. Они включают в себя объекты-члены" как определение для "подобъекта". Если принять вышеуказанное серьезно, это будет означать, что два указателя на два разных подобъекта одного и того же объекта будут представлять один и тот же индекс подписки, даже если "адреса памяти" различны, верно? Тем не менее, без знания размера объектов невозможно определить, указывают ли два указателя с разными значениями на один и тот же объект или нет.
И вот почему я думаю, что этот пункт
Указатели на void (после преобразования указателей) можно сравнить с результатом, определенным следующим образом: если оба указателя представляют один и тот же адрес или оба являются нулевыми значениями указателя, результатом будет true, если оператор равен <= или> =, и false в противном случае.; в противном случае результат не уточняется.
необходимо выразить это - для пустых указателей - отношение может быть гарантировано, только если оба значения указателя фактически указывают на один и тот же адрес памяти.
Чтобы проиллюстрировать это, рассмотрим следующий пример, в котором указатели сравниваются с двумя разными подобъектами одного и того же объекта; попробуйте - я не думаю, что какая-либо реализация могла бы правильно интерпретировать a < b
соответствии с индексным отношением на основе подписки:
bool lessThan(void* a, void*b) {
return a < b;
}
int main() {
struct {
int subobj1;
int subobj2;
} objects[5];
void *subobj1_0 = &objects[0].subobj1;
void *subobj2_0 = &objects[0].subobj2;
bool shouldNotBeLessThan = lessThan(subobj1_0,subobj2_0);
std::cout << "should it be 0 (i.e. not less than)? it is ... " << shouldNotBeLessThan << std::endl;
}
Ответ 4
Формулировка просто неверна, потому что она требует указателей на один и тот же объект или двух нулевых указателей для сравнения неравных с оператором ==
.