В C, являются указателями массивов или используются в качестве указателей?

Насколько я понимаю, массивы были просто постоянными указателями на последовательность значений, и когда вы объявили массив в C, вы объявляли указатель и выделяли пространство для последовательности, на которую он указывает.

Но это меня смущает: следующий код:

char y[20];
char *z = y;

printf("y size is %lu\n", sizeof(y));
printf("y is %p\n", y);
printf("z size is %lu\n", sizeof(z));
printf("z is %p\n", z);

при компиляции с Apple GCC дает следующий результат:

y size is 20
y is 0x7fff5fbff930
z size is 8
z is 0x7fff5fbff930

(моя машина 64 бит, указатели имеют длину 8 байтов).

Если 'y' является константным указателем, то почему он имеет размер 20, например, последовательность значений, на которые он указывает? Является ли имя переменной "y" заменено адресом памяти во время компиляции всякий раз, когда это подходит? Являются ли массивы тогда своего рода синтаксическим сахаром в C, который только что переводится в материал указателя при компиляции?

Ответ 1

Здесь точный язык из стандарта C (n1256):

6.3.2.1 Lvalues, массивы и обозначения функций
...
3 За исключением случаев, когда он является операндом оператора sizeof или унарным оператором & или является строковым литералом, используемым для инициализации массива, выражение, которое имеет массив типов '' типа, преобразуется в выражение с типом ' 'указатель на тип, который указывает на исходный элемент объекта массива и не является значением lvalue. Если объект массива имеет класс хранения регистров, поведение undefined.

Важно помнить, что существует разница между объектом (в терминах C, значением чего-то, что занимает память), и выражением, используемым для ссылки на этот объект.

Когда вы объявляете массив, например

int a[10];

объект, обозначенный выражением a, представляет собой массив (т.е. непрерывный блок памяти, достаточно большой для хранения значений 10 int), а тип выражения a - это "10-элементный массив int" или int [10]. Если выражение a появляется в контексте, отличном от операнда операторов sizeof или &, то его тип неявно преобразуется в int *, а его значение является адресом первого элемента.

В случае оператора sizeof, если операнд является выражением типа T [N], тогда результатом будет количество байтов в объекте массива, а не указатель на этот объект: N * sizeof T.

В случае оператора & это адрес массива, который является таким же, как адрес первого элемента массива, но тип выражения отличается: с учетом объявления T a[N];, тип выражения &a равен T (*)[N] или указателю на N-элементный массив T. Значение такое же, как a или &a[0] (адрес массива совпадает с адресом адрес первого элемента в массиве), но имеет значение разница в типах. Например, с учетом кода

int a[10];
int *p = a;
int (*ap)[10] = &a;

printf("p = %p, ap = %p\n", (void *) p, (void *) ap);
p++;
ap++;
printf("p = %p, ap = %p\n", (void *) p, (void *) ap);

вы увидите вывод порядка

p = 0xbff11e58, ap = 0xbff11e58
p = 0xbff11e5c, ap = 0xbff11e80

IOW, продвижение p добавляет sizeof int (4) к исходному значению, тогда как продвижение ap добавляет 10 * sizeof int (40).

Более стандартный язык:

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

<Б > Ограничения

1 Одно из выражений должно иметь тип '' указатель на тип объекта, другое выражение должно иметь целочисленный тип, а результат имеет тип типа '. <Б > Семантика

2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначением элемента объекта массива. Определение нижнего индекса [] состоит в том, что E1[E2] идентично (*((E1)+(E2))). Из-за правил преобразования, которые применяются к двоичному оператору +, если E1 - объект массива (эквивалентно, указатель на исходный элемент объекта массива) и E2 - целое число, E1[E2] обозначает E2 -й элемент E1 (с нуля).

Таким образом, когда вы индексируете выражение массива, то, что происходит под капотом, заключается в том, что вычисляется смещение от адреса первого элемента в массиве и результат разыменовывается. Выражение

a[i] = 10;

эквивалентно

*((a)+(i)) = 10;

что эквивалентно

*((i)+(a)) = 10;

что эквивалентно

 i[a] = 10;

Да, подстрока массива в C коммутативна; для любви к Богу, никогда не делайте этого в производственном кодексе.

Так как индексирование массива определяется в терминах операций указателя, вы можете применить оператор индекса к выражениям типа указателя, а также типа массива:

int *p = malloc(sizeof *p * 10);
int i;
for (i = 0; i < 10; i++)
  p[i] = some_initial_value(); 

Вот удобная таблица для запоминания некоторых из этих концепций:

Declaration: T a[N];

Expression    Type    Converts to     Value
----------    ----    ------------    -----
         a    T [N]   T *             Address of the first element in a;
                                        identical to writing &a[0]
        &a    T (*)[N]                Address of the array; value is the same
                                        as above, but the type is different
  sizeof a    size_t                  Number of bytes contained in the array
                                        object (N * sizeof T)
        *a    T                       Value at a[0]
      a[i]    T                       Value at a[i]
     &a[i]    T *                     Address of a[i] 

Declaration: T a[N][M];

Expression     Type        Converts to     Value
----------     ----        ------------    -----
          a    T [N][M]    T (*)[M]        Address of the first subarray (&a[0])
         &a    T (*)[N][M]                 Address of the array (same value as
                                             above, but different type)
   sizeof a    size_t                      Number of bytes contained in the
                                             array object (N * M * sizeof T)
         *a    T [M]      T *              Value of a[0], which is the address
                                             of the first element of the first subarray
                                             (same as &a[0][0])
       a[i]    T [M]      T *              Value of a[i], which is the address
                                             of the first element of the i'th subarray
      &a[i]    T (*)[M]                    Address of the i-th subarray; same value as
                                             above, but different type
sizeof a[i]    size_t                      Number of bytes contained in the i'th subarray
                                             object (M * sizeof T)
      *a[i]    T                           Value of the first element of the i'th 
                                             subarray (a[i][0])
    a[i][j]    T                           Value at a[i][j]
   &a[i][j]    T *                         Address of a[i][j]

Declaration: T a[N][M][O];

Expression        Type             Converts to
----------        ----             -----------
         a        T [N][M][O]      T (*)[M][O]
        &a        T (*)[N][M][O]
        *a        T [M][O]         T (*)[O]
      a[i]        T [M][O]         T (*)[O]
     &a[i]        T (*)[M][O]
     *a[i]        T [O]            T *
   a[i][j]        T [O]            T *
  &a[i][j]        T (*)[O]
  *a[i][j]        T 
a[i][j][k]        T

Здесь шаблон четких массивов должен быть ясным.

Итак, в итоге: массивы не являются указателями. В большинстве контекстов выражения массива преобразуются в типы указателей.

Ответ 2

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

Единственное исключение - это операнд оператора sizeof, где результатом является размер массива (в байтах, а не в элементах).

Пара дополнительных вопросов, связанных с этим:

Параметр массива функции - это фикция - компилятор действительно передает простой указатель (это не относится к параметрам reference-to-array в С++), поэтому вы не можете определить фактический размер массива, переданного в function - вы должны передавать эту информацию каким-либо другим способом (возможно, используя явный дополнительный параметр или используя элемент-дозор, как это делают строки C)

Кроме того, общая идиома для получения количества элементов в массиве заключается в использовании макроса вроде:

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))

У этого есть проблема принятия имени массива, где он будет работать, или указателя, где он даст бессмысленный результат без предупреждения от компилятора. Существуют более безопасные версии макроса (особенно для С++), которые будут генерировать предупреждение или ошибку, когда они используются с указателем вместо массива. См. Следующие пункты SO:


Примечание: C99 VLAs (массивы переменной длины) могут не следовать всем этим правилам (в частности, они могут передаваться как параметры с размером массива, известным вызываемой функцией). У меня мало опыта работы с VLA, и насколько я знаю, они широко не используются. Тем не менее, я хочу отметить, что вышеупомянутое обсуждение может применяться по-разному к VLA.

Ответ 3

sizeof оценивается во время компиляции, а компилятор знает, является ли операнд массивом или указателем. Для массивов он задает количество байтов, занятых массивом. Ваш массив равен char[]sizeof(char) равен 1), поэтому sizeof дает вам количество элементов. Чтобы получить число элементов в общем случае, общая идиома (здесь для int):

int y[20];
printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int));

Для указателей sizeof указано количество байтов, занятых сырым типом указателя.

Ответ 5

В

char hello[] = "hello there"
int i;

и

char* hello = "hello there";
int i;

В первом случае (выравнивание дисконтирования) будет сохранено 12 байтов для приветствия с выделенным пространством, инициализированным hello там, а во втором приветствии там хранятся в другом месте (возможно, статическое пространство) и hello инициализируется, чтобы указать на заданная строка.

hello[2], а также *(hello + 2) вернет 'e' в обоих случаях.

Ответ 6

Если 'y' является константным указателем, то почему он имеет размер 20, например, последовательность значений, на которые он указывает?

Потому что z - это адрес переменной и всегда будет возвращать 8 для вашего устройства. Чтобы получить содержимое переменной, вам нужно использовать указатель разметки (&).

EDIT: хорошее различие между ними: http://www.cs.cf.ac.uk/Dave/C/node10.html