В C указатели массивов или используются в качестве указателей? - PullRequest
41 голосов
/ 05 января 2011

Насколько я понимаю, массивы были просто постоянными указателями на последовательность значений, и когда вы объявляли массив в 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 байт)

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

Ответы [ 6 ]

55 голосов
/ 05 января 2011

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

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

Здесь важно помнить, что существует разница между объектом (в терминах 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 Одно из выражений должно иметь указатель типа '' на объект тип '', другое выражение должно иметь целочисленный тип, а результат имеет тип '' type ''.

Семантика

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

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

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

23 голосов
/ 05 января 2011

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

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

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

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

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

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

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


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

6 голосов
/ 05 января 2011

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

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

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

1 голос
/ 05 января 2011

In

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

и

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

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

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

1 голос
/ 05 января 2011

В дополнение к тому, что сказали другие, возможно, эта статья поможет: http://en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

0 голосов
/ 05 января 2011

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

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

РЕДАКТИРОВАТЬ: хорошее различие между ними: http://www.cs.cf.ac.uk/Dave/C/node10.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...