Каково обоснование для одного последнего элемента массива?
Согласно 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 КБ были границы конца памяти. Это одна из причин, по которой юрист говорит вместо того, чтобы говорить "не помещайте массивы в конец памяти" на простом английском языке.