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

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

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

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

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

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

2) Я выбрал это из этого вопроса.

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

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

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

Ответы

Ответ 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). Возможность опускать * - это удобство, но не потрясающее. Это, вероятно, сочетание исторической инерции и согласованности с обработкой массивов.

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

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