Должен ли указатель за концом последнего элемента массива сравниваться с указателем за концом всего массива? - PullRequest
5 голосов
/ 13 января 2020

Компиляторы расходятся при компиляции кода

int main()
{
    constexpr int arr[3] = {};
    static_assert((void*)(arr + 3) == (void*)(&arr + 1));
}

В G CC и Clang static_assert не срабатывает, MSV C считает, что static_assert не удается https://godbolt.org/z/dHgmEN

Должен (void*)(arr + 3) == (void*)(&arr + 1) оценить до true и почему?

1 Ответ

3 голосов
/ 13 января 2020

Соответствующие правила из последнего стандартного черновика:

[intro.object]

Объекты могут содержать другие объекты, называемые подобъектами. Субобъект может быть подобъектом-членом ([class.mem]), подобъектом базового класса ([class.derived]) или элементом массива. Объект, который не является подобъектом любого другого объекта, называется законченным объектом.

Итак, элементы массива являются подобъектами. Массив в этом примере является законченным объектом.


[expr.eq]

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

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

  • В противном случае, если ... оба представляют один и тот же адрес, они сравниваются равными.

79) Как указано в [ basi c .compound], объект, который не является элементом массива, считается для этой цели принадлежащим одноэлементному массиву

Первый случай почти совпадает, но не совсем. Оба являются указателями на один за последним элементом другого завершенного объекта - один завершенный объект - это массив arr, другой - гипотетический одноэлементный массив, элемент которого равен arr. И ни один из них не является указателем на несвязанный завершенный объект, который может существовать после их соответствующего массива, как пояснено в примечании в следующей цитате [basi c .compound].

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


[basi c .compound]

Значение типа указателя, которое является указателем на конец объекта или после него представляет адрес первого байта в памяти ([intro.memory]), занятого объектом 43 или первого байта в памяти после окончания памяти, занимаемой объектом, соответственно. [Примечание: указатель за концом объекта ([expr.add]), как полагают, не указывает на несвязанный объект типа объекта, который может быть расположен по этому адресу. Значение указателя становится недействительным, когда хранилище, которое он обозначает, достигает конца своего срока хранения; см. [basi c .stc]. - примечание к концу]

Для целей ... сравнения ([expr.rel], [expr.eq]), a указатель за концом последнего элемента массива x из n элементов считается эквивалентным указателю на гипотетический элемент массива n с x и объектом типа T, который не является элементом массива, считается принадлежащим массиву с одним элементом типа T . ...

43) Для объекта, который не находится в пределах его времени жизни, это первый байт в памяти, который он будет занимать или занимать.

И arr, и гипотетический массив, содержащий всего arr, имеют одинаковый адрес и одинаковый размер. Поэтому один элемент после обоих массивов считается эквивалентным указателю по тому же адресу за пределами массива.


Просто чтобы было ясно, что массив не может содержать отступы:

[expr.sizeof]

... При применении к классу результатом является количество байтов в объекте этого класса, включая любые отступы, необходимые для размещения объектов этого типа в массиве. , Результатом применения sizeof к потенциально перекрывающемуся подобъекту является размер типа, а не размер подобъекта. При применении к массиву результатом является общее количество байтов в массиве. Это означает, что размер массива из n элементов в n раз больше размера элемента.


Уточним, что преобразование в void* не должно изменять значение указателя и, следовательно, равенство .

[conv.ptr]

Значение типа «указатель на cv T», где T - тип объекта, может быть преобразовано в значение типа «указатель на cv void». Значение указателя ([basi c .compound]) не изменяется при этом преобразовании.

...