Возьмите адрес элемента массива "один конец прошлого" через индекс: легальный по стандарту С++ или нет?

Я уже несколько раз утверждал, что следующий код не разрешен стандартом С++:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Является ли &array[5] законным кодом С++ в этом контексте?

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

Было бы также интересно узнать, соответствует ли он стандарту C. И если это не стандартный С++, почему было принято решение рассматривать его иначе, чем array + 5 или &array[4] + 1?

Ответ 1

Ваш пример легален, но только потому, что вы на самом деле не используете указатель за пределами границ.

Сначала разрешите использовать указатели за пределами границ (потому что так, как я изначально интерпретировал ваш вопрос, прежде чем я заметил, что вместо этого вместо примера используется указатель с одним концом):

В общем, вам даже не разрешено создавать указатель вне пределов. Указатель должен указывать на элемент внутри массива или один за концом. Нигде.

Указателю даже не разрешено существовать, а это значит, что вам также не разрешено разыгрывать его.

Здесь, что стандарт должен сказать по этому вопросу:

5,7: 5:

Когда выражение, имеющее интеграл тип добавляется или вычитается из указатель, результат имеет вид операнд указателя. Если указатель операнд указывает на элемент массив, массив массивный достаточно, результат указывает на смещение элемента от оригинала элемент, так что разность индексы полученного и исходные элементы массива равны интегральное выражение. Другими словами, если выражение P указывает на i-й элемент объекта массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) указывают соответственно на я + n-й и i-n-й элементы массив, если они существуют. Более того, если выражение P точек к последнему элементу массива объект, выражение (P) +1 указывает один за последним элементом массива объекта, а если выражение Q точек один за последним элементом массива объект, выражение (Q) -1 указывает на последний элемент объекта массива. Если и операнд указателя, и результат указывает на элементы того же объект массива или один последний элемент объекта массива, оценка не должна над потоком; в противном случае, поведение недеформированной определено.

(акцент мой)

Конечно, это для оператора+. Поэтому, чтобы быть уверенным, вот что говорит стандарт о подписи текста массива:

5.2.1:1:

Выражение E1[E2] идентично (по определению) до *((E1)+(E2))

Конечно, есть очевидная оговорка: ваш пример на самом деле не показывает указатель вне пределов. он использует указатель "один за конец", который отличается. Указателю разрешено существовать (как сказано выше), но стандарт, насколько я вижу, ничего не говорит о разыменовании его. Самое близкое, что я могу найти, это 3.9.2: 3:

[Примечание: например, адрес, следующий за концом массива (5.7), будет рассмотрен указывают на несвязанный объект типа элементов массивов, который может быть расположен по этому адресу. -end note]

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

Благодаря ilproxyil для исправления последнего бит здесь, отвечая на последнюю часть вашего вопроса:

  • array + 5 фактически не разыгрывать что угодно, просто создает указатель на один за концом array.
  • &array[4] + 1 различия array+4 (что совершенно безопасно), берет адрес этого lvalue и добавляет один к этому адресу, который приводит к указателю с одним концом конца (но этот указатель никогда не получает разыменованный.
  • &array[5] массив разделов + 5 (что, насколько я вижу, является законным, и приводит к "несвязанному объекту типа элементов массивов ", поскольку выше указанного), а затем принимает адрес этого элемента, который также кажется достаточно законным.

Таким образом, они не делают то же самое, хотя в этом случае конечный результат тот же.

Ответ 2

Да, это законно. Из черновик проекта C99:

§6.5.2.1, пункт 2:

Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначение элемента объекта массива. Определение индексного оператора []что E1[E2] совпадает с (*((E1)+(E2))). Из-за правил преобразования, которые примените к двоичному оператору +, если E1 - объект массива (эквивалентно, указатель на начальный элемент объекта массива), а E2 - целое число, E1[E2] обозначает E2 -th элемент E1 (отсчет с нуля).

§6.5.3.2, пункт 3 (акцент мой):

Унарный оператор & дает адрес своего операнда. Если операнд имеет тип типа '', результат имеет тип '' указатель на тип. Если операнд является результатом унарного оператора *ни тот оператор, ни оператор & не оцениваются, и результат выглядит так, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются, и результат не является именующий. Точно так же , если операнд является результатом оператора [], ни оператор, ни унарный *, который подразумевается [], не оцениваются, а результат выглядит так, как если бы оператор &были удалены, а оператор [] был заменен на оператор +. В противном случае результат указатель на объект или функцию, назначенные его операндом.

§6.5.6, пункт 8:

Когда выражение, которое имеет целочисленный тип, добавляется или вычитается из указателя, result имеет тип операнда указателя. Если операнд указателя указывает на элемент объект массива и массив достаточно велик, результат указывает на смещение элемента от исходный элемент такой, что разность индексов результирующего и оригинального элементы массива равны целочисленному выражению. Другими словами, если выражение P указывает на i -й элемент объекта массива, выражения (P)+N (эквивалентно, N+(P)) и (P)-N (где N имеет значение N) указывает соответственно на i+n -th и i−n -th элементы объект массива, если они существуют. Более того, если выражение P указывает на последнее элемент объекта массива, выражение (P)+1 указывает один за последним элементом массив, и если выражение Q указывает один за последним элементом объекта массива, выражение (Q)-1 указывает на последний элемент объекта массива. Если оба указателя операнд и результат указывают на элементы одного и того же объекта массива или один за последним элемент объекта массива, оценка не должна приводить к переполнению; в противном случае поведение undefined. Если результат указывает один за последний элемент объекта массива, он не будет использоваться в качестве операнда унарного оператора *, который оценивается.

Обратите внимание, что стандарт явно позволяет указателям указывать один элемент за конец массива при условии, что они не разыменовываются. В силу 6.5.2.1 и 6.5.3.2 выражение &array[5] эквивалентно &*(array + 5), что эквивалентно (array+5), которое указывает один за концом массива. Это не приводит к разыменованию (на 6.5.3.2), поэтому оно является законным.

Ответ 3

Он юридический.

Согласно документации gcc для С++, &array[5] является законным. В С++ и в C вы можете безопасно обращаться к элементу, расположенному за концом массива, - вы получите действительный указатель. Таким образом, &array[5] как выражение является законным.

Тем не менее, по-прежнему поведение undefined заключается в попытке привязки указателей разнесения к нераспределенной памяти, даже если указатель указывает на действительный адрес. Таким образом, попытка разыменования указателя, сгенерированного этим выражением, по-прежнему остается undefined поведением (т.е. Незаконным), даже если сам указатель действителен.

На практике я предполагаю, что это обычно не приведет к сбою.

Изменить: Кстати, как правило, как итератор end() для контейнеров STL реализуется (как указатель на один конец в конце), так что довольно хорошее свидетельство того, что практика является законной.

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

Ответ 4

Я считаю, что это законно, и это зависит от преобразования "lvalue to rvalue". Последняя строка Основная проблема 232 имеет следующее:

Мы согласились, что подход в стандарте выглядит нормально: p = 0; *п; по своей сути не является ошибкой. Преобразование lvalue-to-rvalue даст ему undefined поведение

Хотя это немного другой пример, то, что он показывает, заключается в том, что '*' не приводит к преобразованию lvalue в rvalue и поэтому, учитывая, что выражение является непосредственным операндом '&' который ожидает lvalue, тогда поведение определено.

Ответ 5

Я не считаю, что это незаконно, но я верю, что поведение массива [5] равно undefined.

  • 5.2.1 [expr.sub] E1 [E2] идентичен (по определению): * ((E1) + (E2))

  • 5.3.1 [expr.unary.op] унарный * оператор... результат - это lvalue, относящийся к объекту или функции, к которым относится выражение.

В этом случае у вас есть поведение undefined, потому что выражение ((E1) + (E2)) фактически не указывает на объект, и стандарт говорит, что должен быть результатом, если это не так.

  • 1.3.12 [defns.undefined] undefined поведение также можно ожидать, если в этом Международном стандарте отсутствует описание любого явного определения поведения.

Как отмечалось в других разделах, array + 5 и &array[0] + 5 являются допустимыми и четко определенными способами получения указателя один за концом массива.

Ответ 6

В дополнение к приведенным выше ответам я укажу оператор & может быть переопределено для классов. Поэтому, даже если это было действительно для POD, это, вероятно, не очень хорошая идея для объекта, который, как вы знаете, недействителен (в первую очередь, как переопределяющий оператор &()).

Ответ 7

Это законно:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Раздел 5.2.1 Подпись. Выражение E1 [E2] идентично (по определению) к * ((E1) + (E2))

Таким образом, мы можем сказать, что array_end также эквивалентен:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))

Раздел 5.3.1.1 Унарный оператор '*': оператор унарного * выполняет косвенное направление: выражение, к которому оно применяется, должно быть указателем на тип объекта или указатель на тип функции, а результат - это lvalue, относящийся к объекту или функции, к которому относится выражение. Если тип выражения является "указателем на T", то тип результата будет "T". [Примечание: указатель на неполный тип (другой чем cv void) могут быть разыменованы. Полученное таким образом lval может использоваться ограниченным образом (для инициализации ссылки, для пример); это значение lvalue не должно быть преобразовано в r-значение, см. 4.1. - конечная нота]

Важная часть выше:

'результат - это lvalue, относящийся к объекту или функции.

Унарный оператор '*' возвращает значение l, относящееся к int (без отказа). Унарный оператор '&' затем получает адрес lvalue.

Пока нет де-ссылки на указатель за пределами границ, операция полностью покрывается стандартом и определяется все поведение. Таким образом, по моему мнению, это полностью законно.

Тот факт, что многие алгоритмы STL зависят от корректного определения поведения, является своего рода намеком на то, что комитет по стандартам уже имеет это, и я уверен, что есть что-то, что явно охватывает это.

В следующем разделе комментариев представлены два аргумента:

(пожалуйста, прочитайте: но он длинный, и мы оба в конечном итоге троллический)

Аргумент 1

это незаконно из-за раздела 5.7, пункт 5

Когда выражение, которое имеет интегральный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд указателя указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующих и исходных элементов массива равна интегральному выражению. Другими словами, если выражение P указывает на i-й элемент объекта массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) соответственно, я + n-й и i-й элементы массива, если они существуют. Более того, если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает один за последним элементом объекта массива, и если выражение Q указывает один за последним элементом объекта массива, выражение (Q) -1 указывает на последний элемент объекта массива. Если оба операнда указателя и результат указывают на элементы одного и того же объекта массива или одно прошлое последний элемент объекта массива, оценка не должна приводить к переполнению; в противном случае поведение undefined.

И хотя этот раздел имеет значение; он не показывает поведение undefined. Все элементы в массиве, о которых мы говорим, находятся либо внутри массива, либо один за концом (который хорошо определен в предыдущем абзаце).

Аргумент 2:

Второй аргумент, представленный ниже, следующий: * - оператор де-ссылки.
И хотя это общий термин, используемый для описания оператора "*"; этот термин намеренно избегается в стандарте, поскольку термин "де-ссылка" не определен в терминах языка и что это означает для основного оборудования.

Хотя доступ к памяти один за концом массива определенно undefined. Я не уверен, что unary * operator обращается к памяти (читает/записывает в память) в этом контексте (не так, как это определяет стандарт). В этом контексте (как определено стандартом (см. 5.3.1.1)) unary * operator возвращает a lvalue referring to the object. В моем понимании языка это не доступ к основной памяти. Результат этого выражения затем сразу используется оператором unary & operator, который возвращает адрес объекта, на который ссылается lvalue referring to the object.

Представлены многие другие ссылки на Википедию и неканонические источники. Все это я считаю неуместным. С++ определяется стандартным.

Вывод:

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

  • Оставьте ответ.
  • Положите все шапки, это глупо, и я ошибаюсь, чтобы читать все.

Это не аргумент:

Не все во всем мире определяется стандартом С++. Откройте свой ум.

Ответ 8

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

Изменить: если вы хотите, чтобы он был симметричным, вы можете написать

int* array_begin = array; 
int* array_end = array + 5;

Ответ 9

Рабочий черновик (n2798):

"Результат унарного и оператора указатель на его операнд. Операнд должен быть lvalue или qualid-id. В первом случае, если тип выражение" T ", тип результатом является" указатель на T." (стр. 103)

array [5] не является квалифицированным идентификатором, насколько я могу это сказать (список приведен на стр. 87); наиболее близким будет идентификатор, но в то время как массив является массивом идентификаторов [5], это не так. Это не значение lvalue, потому что "значение l относится к объекту или функции" (стр. 76). array [5], очевидно, не является функцией и не гарантируется ссылка на действительный объект (потому что массив + 5 после последнего выделенного элемента массива).

Очевидно, что он может работать в определенных случаях, но он недействителен С++ или безопасен.

Примечание. Чтобы получить один за ним массив (стр. 113), можно добавить:

", если выражение P [указатель] указывает на последний элемент массива объект, выражение (P) +1 указывает один за последним элементом массива объекта, а если выражение Q точек один за последним элементом массива объект, выражение (Q) -1 указывает на последний элемент объекта массива. Если и операнд указателя, и результат указывает на элементы того же объект массива или один последний элемент объекта массива, оценка не должна над потоком"

Но это не является законным для этого, используя &.

Ответ 10

Это должно быть поведение undefined по следующим причинам:

  • Попытка доступа к элементам вне границ приводит к поведению undefined. Следовательно, стандарт не запрещает реализацию, исключающую исключение в этом случае (то есть границы проверки реализации перед доступом к элементу). Если & (array[size]) были определены как begin (array) + size, реализация, бросающая исключение в случае внеочередного доступа, больше не будет соответствовать стандарту.

  • Невозможно сделать этот выход end (array), если массив не является массивом, а скорее произвольным типом коллекции.

Ответ 11

Для С++ ответ, кажется, здесь:

http://eel.is/c++draft/conv#lval-2

Когда преобразование lvalue-rval применяется к выражению e и либо (2.1) e не оценивается потенциально, либо (2.2) оценка из e приводит к оценке члена ex из набора потенциала результаты e и ex называет переменную x, которая не является odr-используемой ex, значение, содержащееся в ссылочном объекте, не доступно.

Ответ 12

Стандарт С++, 5.19, пункт 4:

Выражение константы адреса является указателем на lvalue.... Указатель должен быть создан явно, используя унарный и оператор... или используя выражение типа array (4.2).... Оператор subscriptip []... может использоваться при создании выражения константы адреса, но при использовании этих операторов не следует обращаться к значению объекта. Если используется оператор подписи, один из его операндов должен быть интегральным постоянным выражением.

Мне кажется, что & array [5] является законным С++, являющимся выражением константы адреса.

Ответ 13

Если ваш пример НЕ является общим, а конкретным, то он разрешен. Вы можете легально, AFAIK, перемещаться мимо выделенного блока памяти. Это не работает для общего случая, хотя я пытаюсь получить доступ к элементам дальше на 1 из конца массива.

Только что просмотрел C-Faq: текст ссылки

Ответ 14

Это совершенно законно.

Класс vector < > template из stl делает именно это, когда вы вызываете myVec.end(): он получает указатель (здесь как итератор), который указывает один элемент за конец массива.