В 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, который только что переводится в материал указателя при компиляции?

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

6.3.2.1 Lvalues, массивы и указатели функций

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

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

Когда вы объявляете массив, такой как

 int a[10]; 

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

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

В случае оператора & this это адрес массива, который совпадает с адресом первого элемента массива, но тип выражения отличается: с учетом объявления T a[N]; , тип выражения &aT (*)[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(); 

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

 Декларация: T a [N];

 Тип выражения преобразуется в значение
 ---------- ---- ------------ -----
          a T [N] T * Адрес первого элемента в a;
                                         идентичный записи & a [0]
         & a T (*) [N] Адрес массива;  значение одинаково
                                         как указано выше, но тип отличается
   sizeof size_t Количество байтов, содержащихся в массиве
                                         объект (N * sizeof T)
         * значение T в точке [0]
       a [i] T Значение при a [i]
      & a [i] T * Адрес [i] 

 Декларация: T a [N] [M];

 Тип выражения преобразуется в значение
 ---------- ---- ------------ -----
           a T [N] [M] T (*) [M] Адрес первого подмассива (& a [0])
          & a T (*) [N] [M] Адрес массива (то же значение, что и
                                              выше, но другого типа)
    sizeof size_t Количество байтов, содержащихся в
                                              объект массива (N * M * sizeof T)
          * a T [M] T * Значение a [0], которое является адресом
                                              первого элемента первого подмассива
                                              (такой же, как & a [0] [0])
        a [i] T [M] T * Значение a [i], которое является адресом
                                              первого элемента i-го подмассива
       & a [i] T (*) [M] Адрес i-го подмассива;  то же значение, что и
                                              выше, но другого типа
 sizeof a [i] size_t Число байтов, содержащихся в i-м подмассиве
                                              объект (M * sizeof T)
       * a [i] T Значение первого элемента i'th 
                                              subarray (a [i] [0])
     a [i] [j] T Значение при a [i] [j]
    & a [i] [j] T * Адрес [i] [j]

 Декларация: T a [N] [M] [O];

 Тип выражения Преобразует в
 ---------- ---- -----------
          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

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

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

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

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

Еще пара вопросов, связанных с этим:

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

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

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

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

  • Версия на C ++
  • лучше (хотя все еще не совсем безопасно) версия C

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

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

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

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

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

В

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

а также

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

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

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

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

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

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

  • преобразование двумерного массива в указатель на указатель
  • Как delete знает, что это массив?
  • В чем разница между char a =? String ?; и char * p =? string?;?
  • Как происходит разыменование функционального указателя?
  • Когда следует использовать новое ключевое слово в C ++?
  • Почему int указатель «++» увеличивается на 4, а не на 1?
  • Скрытие нулевых значений, понимание того, почему голанг не удается здесь
  • Указатели, умные указатели или общие указатели?
  • Разница указателей / адресов
  • В C, какой правильный синтаксис для объявления указателей?
  • Передача ссылок на указатели в C ++
  • Давайте будем гением компьютера.