Массив, объявленный как int v [100], но & (v [100]) не дает предупреждения

У меня есть следующая программа:

#include <stdio.h>

int main() {

    int v[100];
    int *p;

    for (p = &(v[0]); p != &(v[100]); ++p)
        if ((*p = getchar()) == EOF) {
            --p;
            break;
        }

    while (p != v)
        putchar(*--p);

    return 0;
}

И это вывод gcc --version на терминале:

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix

Почему получение адреса элемента после последнего массива не дает мне никаких предупреждений, но, например, адрес v[101] дает мне следующее предупреждение

test.c:8:29: warning: array index 101 is past the end of the array (which
      contains 100 elements) [-Warray-bounds]
    for(p = &(v[0]); p != &(v[101]); ++p)
                            ^ ~~~
test.c:5:5: note: array 'v' declared here
    int v[100];
    ^
1 warning generated.

Я знаю, что элементы индексации из границ буфера - это поведение undefined, поэтому почему компилятор не жалуется на первый случай?

Ответ 1

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

N1256 6.5.2.1 Подписывание массива

Определение индексного оператора [] состоит в том, что E1 [E2] идентичен (* ((E1) + (E2))).

N1256 6.5.3.2 Операторы адреса и косвенности

Если операнд является результатом унарного * оператора, ни тот оператор, ни оператор и не оцениваются, и результат выглядит так, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются, и результат не является именующий. Аналогично, если операнд является результатом оператора [], ни оператор, ни оператор, ни унарный *, который подразумевается [], оценивается, и результат выглядит так, как если бы оператор и были удалены, а оператор [] был заменен на оператор +.

N1256 6.5.6 Аддитивные операторы

Более того, если выражение P указывает на последнее элемент объекта массива, выражение (P) +1 указывает один за последним элементом массив, и если выражение Q указывает один за последним элементом объекта массива, выражение (Q) -1 указывает на последний элемент объекта массива

Ответ 2

Это о совместимости с ненадежным письменным кодом.

Как цитируется MikeCAT, для массива int ar[N] выражение ar+N является допустимым и приводит к указателю, указывающему на прошлое-конечное положение. Хотя этот указатель не может быть разыменован, его можно сравнить с любым другим указателем в массив, который позволяет вам написать хороший цикл for (p = ar; p != ar+N; ++p).

Кроме того, программистам нравится писать читаемый код и, возможно, если вы хотите указатель на i -й элемент массива, запись &ar[i] более четко отражает ваше намерение, чем запись ar + i.

Объедините эти два, и вы получите программистов, которые пишут &ar[N], чтобы получить указатель прошлого конца, и хотя это технически доступ к недопустимому индексу массива, ни один компилятор никогда не реализует это как что-либо еще, кроме ar + N - на самом деле, компилятор должен был бы отклониться от своего пути, чтобы сделать это по-другому. Довольно далеко.

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