Каковы различия между a + i и & a [i] для арифметики указателей в C ++? - PullRequest
0 голосов
/ 01 марта 2019

Предположим, у нас есть:

char* a;
int   i;

Многие введения в C ++ (например, этот ) предполагают, что значения a+i и &a[i] являются взаимозаменяемыми.Я наивно верил этому в течение нескольких десятилетий, пока недавно не наткнулся на следующий текст ( здесь ), цитируемый, в частности, [dcl.ref] :

пустая ссылка не может существовать в четко определенной программе, потому что единственный способ создать такую ​​ссылку - привязать ее к «объекту», полученному путем разыменования нулевого указателя, что вызывает неопределенное поведение.

Другими словами, «привязка» ссылочного объекта к нулевому разыменованию приводит к неопределенному поведению.Основываясь на контексте вышеприведенного текста , можно сделать вывод, что просто , оценивающий &a[i] (в макросе offsetof), считается "обязательной" ссылкой.Кроме того, кажется, что существует консенсус, что &a[i] вызывает неопределенное поведение в случае, когда a=null и i=0.Это поведение отличается от a+i (по крайней мере в C ++, в случае a = null, i = 0 ).

Это приводит как минимум к 2 вопросам о различиях между a+i и &a[i]:

Во-первых, какова основная семантическая разница между a+i и &a[i], которая вызывает эту разницу в поведении.Может ли это быть объяснено в терминах каких-либо общих принципов, а не просто «привязка ссылки к нулевому объекту разыменования приводит к неопределенному поведению только потому, что это очень специфический случай, который все знают»?Может быть, &a[i] может генерировать доступ к памяти для a[i]?Или автор спецификации не был доволен нулевыми разыменованиями в тот день?Или что-то еще?

Во-вторых, кроме случая, когда a=null и i=0, есть ли другие случаи, когда a+i и &a[i] ведут себя по-разному?(может быть охвачен первым вопросом, в зависимости от ответа на него.)

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

TL; DR: a+i и &a[i] являются правильно сформированными и дают нулевой указатель, когда a является нулевым указателем и i равно 0, в соответствии со (намерением) стандарта, ивсе составители согласны.


a+i явно правильно сформировано в соответствии с [expr.add] / 4 последнего проекта стандарта:

Когда выражение J, имеющее целочисленный тип, добавляется или вычитается из выражения P типа указателя, результат имеет тип P.

  • Если P оценивается как нулевое значение указателя, а J оценивается как0, результатом является нулевое значение указателя.
  • [...]

&a[i] сложно.Для [expr.sub] / 1 , a[i] эквивалентно *(a+i), таким образом &a[i] эквивалентно &*(a+i).Теперь в стандарте не совсем ясно, правильно ли сформирован &*(a+i), когда a+i является нулевым указателем.Но, как указывает @nm в комментарии , цель, записанная в cwg 232 , состоит в том, чтобы разрешить этот случай.


Поскольку базовый язык UB требуется длябыть пойманным в константном выражении ( [expr.const] / (4.6) ), мы можем проверить, считают ли компиляторы эти два выражения UB.

Вот демонстрация, если компиляторы думаютконстантное выражение в static_assert равно UB, или если они думают, что результат не true, то они должны произвести диагностику (ошибку или предупреждение) по стандарту:

(обратите внимание, что это используетоднопараметрический static_assert и constexpr lambda, которые являются функциями C ++ 17, и лямбда-аргумент по умолчанию, который также является довольно новым)

static_assert(nullptr == [](char* a=nullptr, int i=0) {
    return a+i;
}());

static_assert(nullptr == [](char* a=nullptr, int i=0) {
    return &a[i];
}());

С https://godbolt.org/z/hhsV4I, кажется, что все компиляторы ведут себя одинаковов этом случае вообще не производит диагностики (что меня немного удивляет).


Однако это отличается от случая offset.Реализация, опубликованная в , в этом вопросе явно создает ссылку (которая необходима для обхода пользовательского operator&) и, таким образом, подчиняется требованиям к ссылкам.

0 голосов
/ 01 марта 2019

В стандарте C ++ в разделе [expr.sub] / 1 вы можете прочитать:

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

Это означает, что &a[i] в точности совпадает с &*(a+i).Таким образом, вы должны разыменовать * указатель первым и получить адрес & второго.В случае, если указатель недопустим (то есть nullptr, но также находится вне диапазона), это UB.

a+i основан на арифметике указателей.Сначала это выглядит менее опасно, поскольку нет разыменования, которое наверняка было бы UB.Однако это также может быть UB (см. [expr.add] / 4 :

Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результатимеет тип операнда указателя. Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно,-гипотетический) элемент x [i + j], если 0 ≤ i + j ≤ n; в противном случае поведение не определено . Аналогично, выражение P - J указывает на (возможно, гипотетический) элемент x [i - j], если 0 ≤ i - j ≤ n, в противном случае поведение не определено.

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

...