В Си гарантируется, что начальный адрес массива меньше, чем адреса других элементов?
Другими словами, когда делаете
index = &array[x] - &array[0];
Всегда ли гарантировано (по стандарту C), что &array[0] <= &array[x], или это зависит от компилятора? Какие главы стандарта C актуальны для этой темы?
7 ответов
Порядок адреса гарантирован. Поведение реляционных операторов определено в C11 6.5.8p5:
[...] указатели на элементы массива с большими значениями индекса ниже, чем указатели, на элементы того же массива с более низкими значениями индекса. [...]
таким образом &array[x] >= &array[0]
всегда верно, если x
является индексом элемента или индексом, превышающим максимальный индекс. (И если x
указывает на фактический массив, тогда поведение не определено.)
Но удивительно разница &array[x] - &array[0]
определяется только тогда, когда
x
является фактическим индексом элемента или единицей, превышающей максимальный индекс в массиве иx
не больше чемPTRDIFF_MAX
так как есть своеобразный угловой корпус: C11 6.5.6p9 говорит, что
9 Когда вычтены два указателя, оба должны указывать на элементы одного и того же объекта массива или один за последним элементом последнего объекта массива; Результатом является разница индексов двух элементов массива. Размер результата определяется реализацией, а его тип (целочисленный тип со знаком)
ptrdiff_t
определены в<stddef.h>
заголовок. Если результат не может быть представлен в объекте этого типа, поведение не определено. Другими словами, если выражения P и Q указывают, соответственно, на i-й и j-й элементы объекта массива, выражение (P)-(Q) имеет значение ij, если значение вписывается в объект типptrdiff_t
, [...]
Если подписано ptrdiff_t
такой же ширины, как и без знака size_t
, можно иметь массив, для которого существует индекс x
лучше чем PTRDIFF_MAX
; затем &array[x] >= &array[0]
все же, но &array[x] - &array[0]
имеет совершенно неопределенное поведение.
Вот демонстрация. Мой компьютер x86-64 работает под управлением 64-битной Ubuntu Linux, но он также способен запускать 32-битные программы. В 32-битном X86 Linux + GCC, ptrdiff_t
32-разрядное целое число со знаком и size_t
32-разрядное целое число без знака. Программа, работающая в 64-битном Linux в 32-битном режиме, может легко выделить более 2G памяти с помощью malloc, так как все адресное пространство 4G зарезервировано для пользовательского режима.
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stddef.h>
int main(void) {
size_t size = (size_t)PTRDIFF_MAX + 2;
size_t x = (size_t)PTRDIFF_MAX + 1;
char *array = malloc(size);
if (! array) {
perror("malloc");
exit(1);
}
array[0] = 42;
array[x] = 84;
printf("&array[0]: %p\n", (void *)&array[0]);
printf("&array[x]: %p\n", (void *)&array[x]);
printf("&array[x] >= &array[0]: %d\n", &array[x] >= &array[0]);
printf("&array[x] - &array[1]: %td\n", &array[x] - &array[1]);
printf("&array[x] - &array[0]: %td\n", &array[x] - &array[0]);
printf("(&array[x] - &array[0]) < 0: %d\n", (&array[x] - &array[0]) < 0);
}
Затем компилируется для 32-битного режима и запускается:
% gcc huge.c -m32 -Wall && ./a.out
&array[0]: 0x77567008
&array[x]: 0xf7567008
&array[x] >= &array[0]: 1
&array[x] - &array[1]: 2147483647
&array[x] - &array[0]: -2147483648
(&array[x] - &array[0]) < 0: 1
Память была выделена успешно, начальный адрес 0x77558008, &array[x]
я сидела 0xf7504008
, &array[x]
больше, чем &array[0]
, Различия &array[x] - &array[1]
дал положительный результат, тогда как &array[x] - &array[0]
, с его неопределенным поведением, теперь дал отрицательный результат!
Прежде всего, FWIW, цитируя C11
, глава §6.5.6 / P9, (emphsis моя)
Когда вычтены два указателя, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемента объекта массива; Результатом является разница индексов двух элементов массива. [...]
Таким образом, вам не нужно беспокоиться об отдельном значении указателя (позиционировании). Это разница, которая имеет значение (например, что-то вроде |a-b|
)
Тем не менее, если он должен прийти к "сравнению", (использование реляционных операторов, <
, >
, <=
, >=
), стандарт говорит,
Когда сравниваются два указателя, результат зависит от относительного расположения в адресном пространстве указанных объектов. [....] Если указанные объекты являются членами одного и того же агрегатного объекта, [...] и указатели на элементы массива с большими значениями нижнего индекса сравниваются больше, чем указатели, на элементы того же массива с более низкими значениями нижнего индекса. [....]
Итак, для такого заявления, как &array[x] <= &array[0]
, это будет оценивать 0
(ЛОЖЬ), когда x > 0
,
Да потому, что &array[x]
определяется как эквивалент array+x
,
Выражение постфикса, за которым следует выражение в квадратных скобках [], является подписанным обозначением элемента объекта массива. Определение оператора индекса [] заключается в том, что E1[E2] идентичен (* ((E1) + (E2))). Из-за правил преобразования, которые применяются к бинарному оператору +, если E1 является объектом массива (эквивалентно указателю на начальный элемент объекта массива), а E2 является целым числом, E1[E2] обозначает E2-й элемент Е1 (считая с нуля).
Стандарт C11 определяет разность адресов между элементами массива как число, которое зависит от относительного (логического) порядка элементов. Как указано в описании аддитивных операторов:
Когда вычтены два указателя, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемента объекта массива; Результатом является разница индексов двух элементов массива. Размер результата определяется реализацией, а его тип (целочисленный тип со знаком) ptrdiff_t определен в заголовке. Если результат не может быть представлен в объекте этого типа, поведение не определено. Другими словами, если выражения P и Q указывают, соответственно, на i-й и j-й элементы объекта массива, выражение (P)-(Q) имеет значение ij, если значение вписывается в объект введите ptrdiff_t. Более того, если выражение P указывает либо на элемент объекта массива, либо на один элемент после последнего элемента объекта массива, а выражение Q указывает на последний элемент того же объекта массива, выражение ((Q)+1)-(P) имеет то же значение, что и ((Q)-(P))+1 и -((P)-((Q)+1)), и имеет нулевое значение, если выражение P указывает на одну точку после последний элемент объекта массива, хотя выражение (Q) +1 не указывает на элемент объекта массива.
Таким образом, разница в вашем примере определяется как x - 0
,
Из спецификации C11 (ISO/IEC 9899:2011 (E)) §6.5.8/5:
Когда сравниваются два указателя, ... Если указанные объекты являются членами одного и того же агрегатного объекта, ... и указатели на элементы массива с большими значениями нижнего индекса сравниваются больше, чем указатели, на элементы того же массива с более низкими значениями нижнего индекса.
Это означает, что &array[x] <= &array[0]
будет ложным, если x
равно нулю.
index = &array[x] - &array[0];
является синтаксическим сахаром для
index = (array+x) - (array+0)
потому что в C любой массив десугарируется как указатель.
Теперь с учетом pointer arithmetic
это будет переписано как index = x
Соответствующие темы, которые вы можете найти в Google или искать в ISO9899: pointer arithmetic
и десагеринг массивов как указателей.
Учитывая, что обход массива также может быть достигнут путем увеличения указателя, представляется весьма фундаментальным, что абсолютный адрес последующих индексов увеличивается.
char[] foobar;
char *foobarPtr = foobar;
foobar[0] == *foobarPtr++;
foobar[1] == *foobarPtr++;
https://www.tutorialspoint.com/cprogramming/c_pointer_to_an_array.htm