Ответ 1
Выражение типа массива неявно преобразуется в указатель на первый элемент объекта массива, если только:
- Операнд унарного оператора
&
; - Операнд
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
является незаконным, а не размером указателя).
Что это.
В обоих случаях объект указателя не создается. Выражение преобразуется в значение ( "decays" to) значение указателя, также называемое адресом.
Обратите внимание, что как оператор индексирования массива []
, так и оператор вызова функции ()
требуют указателя. В обычном вызове функции, таком как 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
s); последний имеет тип int*
. Эти два типа несовместимы (вы не можете юридически назначить значение int*
для объекта int(*)[10]
, например), и арифметика указателя ведет себя по-другому на них.
Существует отдельное правило, в котором говорится, что объявленный функциональный параметр массива или типа функции отрегулирован во время компиляции (не преобразован) в параметр указателя. Например:
void func(int arr[]);
в точности эквивалентно
void func(int *arr);
Эти правила (преобразование выражений массива и настройка параметров массива) в совокупности создают большую путаницу в отношении отношения между массивами и указателями в C.
Раздел 6 comp.lang.c FAQ отлично описывает детали.
Определяющим источником для этого является стандарт ISO C. N1570 (1.6 MB 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)
. Возможность опускать *
- это удобство, но не потрясающее. Это, вероятно, сочетание исторической инерции и согласованности с обработкой массивов.