Когда имя массива или имя функции "конвертируется" в указатель? (в С)

1) Заблуждение:

  • Всякий раз, когда массив объявляется на языке Си, неявно создается указатель на первый элемент массива (имя массива). (Это? Я так не думаю!)

  • Первые две строчки этой страницы (хотя я не уверен в правильности информации) говорят о том же.

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

  • Но когда я вывожу адрес, содержащийся в этом указателе, и адрес этого указателя, они окажутся одинаковыми. Итак, я думаю, что указатель не создается в конце концов.

2) Я поднял это из этого вопроса.

  • В большинстве случаев имена массивов преобразуются в указатели.

Кто-нибудь может дать подробное объяснение, КОГДА компилятор решает преобразовать имя массива в указатель, и ПОЧЕМУ?

PS: Пожалуйста, объясните то же самое с функциями. Также в этой ссылке был приведен пример, говорящий, что для функции int square(int,int) любой из square, &square, *square, **square ссылается на тот же указатель функции. Вы можете объяснить?

Изменить: фрагмент кода

int fruits[10];
printf("Address IN constant pointer is %p\n",  fruits);
printf("Address OF constant pointer is %p\n", &fruits); 

Выход:

Address IN constant pointer is 0xbff99ca8
Address OF constant pointer is 0xbff99ca8

4 ответа

Решение

Выражение типа массива неявно преобразуется в указатель на первый элемент объекта массива, если это не так:

  • Операнд одинарного & оператор;
  • Операнд sizeof; или же
  • Строковый литерал в инициализаторе, используемый для инициализации объекта массива.

Примеры третьего случая:

char arr[6] = "hello";

"hello" является выражением массива типа char[6] (5 плюс 1 для '\0' терминатор). Это не преобразовано в адрес; полное 6-байтовое значение "hello" копируется в объект массива arr,

С другой стороны, в этом:

char *ptr = "hello";

выражение массива "hello" "распадается" на указатель на 'h' и это значение указателя используется для инициализации объекта указателя ptr, (Это действительно должно быть const char *ptr, но это побочный вопрос.)

Выражение типа функции (например, имя функции) неявно преобразуется в указатель на функцию, если это не так:

  • Операнд одинарного & оператор; или же
  • Операнд sizeof (sizeof function_name недопустимо, не размер указателя).

Вот и все.

В обоих случаях объект указателя не создается. Выражение преобразуется в ("распадается") значение указателя, также называемое адресом.

Обратите внимание, что оба оператора индексации массива [] и вызов функции "оператор" () Требуется указатель. В обычном вызове функции вроде func(42), имя функции func "распадается" на указатель на функцию, которая затем используется в вызове. (Это преобразование фактически не нужно выполнять в сгенерированном коде, если вызов функции делает правильные вещи.)

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

(В проекте стандарта C11 говорится, что есть еще одно исключение для массивов, а именно, когда массив является операндом нового _Alignof оператор. Это ошибка в черновике, исправленная в окончательном опубликованном стандарте C11; _Alignof может применяться только к имени типа в скобках, но не к выражению.)

Адрес массива и адрес его первого члена:

int arr[10];
&arr;    /* address of entire array */
&arr[0]; /* address of first element */

один и тот же адрес памяти, но они разных типов. Первый является адресом всего объекта массива и имеет тип int(*)[10] (указатель на массив из 10 int с); последний тип int*, Эти два типа несовместимы (вы не можете юридически назначить int* значение для int(*)[10] объект, например), и арифметика указателя ведет себя по-разному на них.

Существует отдельное правило, которое гласит, что объявленный параметр функции массива или типа функции корректируется во время компиляции (не преобразуется) в параметр указателя. Например:

void func(int arr[]);

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

void func(int *arr);

Эти правила (преобразование выражений массива и корректировка параметров массива) в совокупности создают большую путаницу в отношении отношений между массивами и указателями в C.

Раздел 6 FAQ по comp.lang.c отлично объясняет детали.

Окончательным источником для этого является стандарт ISO C. N1570 (1,6 МБ в формате PDF) - последняя версия стандарта 2011 года; эти преобразования указаны в разделе 6.3.2.1, параграфы 3 (массивы) и 4 (функции). Этот проект имеет ошибочную ссылку на _Alignof, который на самом деле не применяется.

Кстати, printf звонки в вашем примере строго некорректны:

int fruits[10];
printf("Address IN constant pointer is %p\n",fruits);
printf("Address OF constant pointer is %p\n",&fruits); 

%p формат требует аргумент типа void*, Если указатели типа int* а также int(*)[10] имеют то же представление, что и void* и передаются в качестве аргументов таким же образом, как и в случае большинства реализаций, это, вероятно, будет работать, но это не гарантируется. Вы должны явно конвертировать указатели в void*:

int fruits[10];
printf("Address IN constant pointer is %p\n", (void*)fruits);
printf("Address OF constant pointer is %p\n", (void*)&fruits);

Так почему же это так? Проблема в том, что массивы в некотором смысле являются гражданами второго сорта в C. Вы не можете передавать массив по значению в качестве аргумента при вызове функции и не можете возвращать его как результат функции. Чтобы массивы были полезны, вам нужно иметь возможность работать с массивами разной длины. отдельный strlen функции для char[1], за char[2], за char[3] и так далее (все это разные типы) было бы невероятно громоздким. Таким образом, вместо этого к массивам обращаются и манипулируют с помощью указателей на их элементы, а арифметика указателей обеспечивает способ обхода этих элементов.

Если выражение массива не распадется на указатель (в большинстве случаев), то вы не сможете ничего сделать с результатом. И C был получен из более ранних языков (BCPL и B), которые не обязательно даже различали массивы и указатели.

Другие языки могут работать с массивами как первоклассными типами, но для этого требуются дополнительные функции, которые не были бы "в духе C", которые по-прежнему остаются языком относительно низкого уровня.

Я менее уверен в обоснованности такого подхода к таким функциям. Это правда, что нет значений типа функции, но язык мог бы потребовать функцию (а не указатель на функцию) в качестве префикса в вызове функции, что требует явного * оператор для косвенного вызова: (*funcptr)(arg), Возможность опустить * это удобство, но не потрясающее. Вероятно, это сочетание исторической инерции и согласованности с обработкой массивов.

Краткий ответ - да... кроме иногда. Обычно после объявления массива каждый раз, когда используется его имя, он преобразуется в указатель на первый элемент объекта массива. Однако в некоторых случаях этого не происходит. Эти случаи, когда этого не происходит, можно найти в ответе @KeithThompson здесь.

Подобно вашему массиву, тип функции также будет преобразован в значение указателя... за исключением иногда. Случаи, когда это не происходит снова, можно найти в ответе @KeithThompson снова. здесь

Существует гораздо лучший способ думать об этом. Выражение типа массива (которое включает в себя: имя массива, разыменование указателя на массив, индексирование двумерного массива и т. Д.) - это просто выражение типа массива. Это не выражение типа указателя. Тем не менее, язык обеспечивает неявное преобразование из выражения типа массива в выражение типа указателя, если он используется в контексте, который хочет указатель.

Вам не нужно помнить, что он преобразуется в указатель "кроме" sizeof, а также &и т. д. Вам просто нужно подумать о контексте выражения.

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

Или, если вы используете выражение массива с оператором разыменования *или арифметические операторы +-или подстрочный оператор, []; все эти операторы работают с указателями, поэтому компилятор снова видит это и применяет преобразование.

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

Когда вы используете его с sizeof, а также &эти контексты имеют смысл для массивов, поэтому компилятор не потрудится применить преобразование. Единственная причина, по которой они рассматриваются как "исключение" при преобразовании массива в указатель, заключается в том, что все остальные контексты выражений (как вы можете видеть в приведенных выше примерах) в C не имеют смысла для типов массивов (массив в C) такие искалеченные типы, и эти немногие являются единственными "оставленными".

Описание, приведенное на связанной странице в первой части вашего вопроса, безусловно, совершенно неверно. Там нет указателя там, постоянный или нет. Вы можете найти исчерпывающее объяснение поведения массива / функции в ответе @KeithThompson.

Кроме того, возможно, имеет смысл добавить (в качестве примечания), что массивы, реализованные в виде двухкомпонентных объектов - именованный указатель, указывающий на независимый безымянный блок памяти, - не являются точно химерными. Они существовали в этой специфической форме в предшественнике языка Си - языке В. И первоначально они были перенесены с B на C совершенно без изменений. Вы можете прочитать об этом в документе Дениса Ритчи " Развитие языка Си" (см. Раздел "Эмбриональный Си").

Однако, как указано в том же документе, этот вид реализации массивов был несовместим с некоторыми новыми функциями языка Си, такими как типы структур. Наличие двухкомпонентных массивов внутри объектов структуры превратит такие объекты в объекты более высокого уровня с нетривиальной конструкцией. Это также сделало бы их несовместимыми с операциями необработанной памяти (например, memcpy и так далее). Такие соображения являются причиной того, что массивы были перепроектированы из объектов, состоящих из двух частей, в их текущую форму, состоящую из одной части. И, как вы можете прочитать в этом документе, редизайн был выполнен с учетом обратной совместимости с массивами в стиле B.

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

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