Когда имя массива или имя функции "конвертируется" в указатель? (в С)
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, полагая, что где-то там спрятан указатель. Поведение современного массива Си было специально разработано, чтобы подражать / поддерживать эту иллюзию. И, во-вторых, некоторые архаичные документы могут все еще содержать остатки той "зачаточной" эпохи (хотя, похоже, документ, который вы связали, не должен быть одним из них).