Каково обоснование для одного последнего элемента массива?

Согласно N1570 (проект C11) 6.5.6/8 Аддитивные операторы:

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

Подраздел 6.5.6/9 также содержит:

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

Это оправдывает правильность арифметики указателя следующим образом:

#include <stdio.h>

int main(void)
{
    int a[3] = {0, 1, 2};
    int *P, *Q;

    P = a + 3; // one past the last element
    Q = a + 2; // last element

    printf("%td\n", ((Q)+1)-(P));
    printf("%td\n", ((Q)-(P))+1);
    printf("%td\n", -((P)-((Q)+1)));

    return 0;
}

Я ожидал бы запретить указывать на элемент массива вне пределов, для которого разыменование действует как неопределенное поведение (переполнение массива), таким образом, это делает его потенциально опасным. Есть ли для этого обоснование?

2 ответа

Решение

Указание диапазона для циклического полузакрытого интервала [start, end)особенно для индексов массива, имеет определенные приятные свойства, как заметил Дейкстра в одной из своих заметок.

1) Вы можете вычислить размер диапазона как простую функцию end - start, В частности, если диапазон указан в терминах индексов массива, число итераций, выполняемых циклом, будет определяться как end - start, Если диапазон был [start, end]то число итераций было бы end - start + 1 - очень раздражает, не так ли?:)

2) Второе наблюдение Дийсктры применимо только к случаю (неотрицательных) интегральных индексов - указание диапазона как [start, end) а также (start, end] оба имеют свойство, указанное в 1). Однако, указав это как (start, end] требует от вас, чтобы индекс -1 представлять диапазон цикла, включая индекс 0 - вы допускаете "неестественную" ценность -1 просто ради представления диапазона. [start, end) Конвенция не имеет этой проблемы, потому что end является неотрицательным целым числом и, следовательно, естественным выбором при работе с индексами массива.

Dijsktra возражает против разрешения -1 действительно имеет сходство с пропуском последнего действительного адреса контейнера. Однако, так как вышеупомянутая конвенция использовалась так долго, она, вероятно, убедила комитет по стандартам сделать это исключение.

Обоснование довольно простое. Компилятору не разрешается помещать массив в конец памяти. Для иллюстрации предположим, что у нас есть 16-разрядный компьютер с 16-разрядными указателями. Низкий адрес 0x0000. Высокий адрес 0xffff. Если вы объявите char array[256] и компилятор находит array по адресу 0xff00, то технически массив поместился бы в памяти, используя адреса 0xff00 через 0xffff включительно. Тем не менее, выражение

char *endptr = &array[256];   // endptr points one past the end of the array

будет эквивалентно

char *endptr = NULL;          // &array[256] = 0xff00 + 0x0100 = 0x0000

Это означает, что следующий цикл не будет работать, так как ptr никогда не будет меньше 0

for ( char *ptr = array; ptr < endptr; ptr++ )

Таким образом, разделы, на которые вы ссылались, просто говорят юристы: "Не помещайте массивы в конец области памяти".


Историческая справка: самые ранние процессоры x86 использовали схему сегментированной памяти, в которой адреса памяти указывались 16-битным регистром указателя и 16-битным регистром сегмента. Конечный адрес был вычислен путем сдвига регистра сегмента влево на 4 бита и добавления к указателю, например

pointer register    1234
segment register   AB00
                   -----
address in memory  AC234

Результирующее адресное пространство составляло 1 МБ, но каждые 64 КБ были границы конца памяти. Это одна из причин, по которой юрист говорит вместо того, чтобы говорить "не помещайте массивы в конец памяти" на простом английском языке.

Другие вопросы по тегам