В Си гарантируется, что начальный адрес массива меньше, чем адреса других элементов?

Другими словами, когда делаете

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,

6.5.2.1p2:

Выражение постфикса, за которым следует выражение в квадратных скобках [], является подписанным обозначением элемента объекта массива. Определение оператора индекса [] заключается в том, что 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

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