Почему адрес массива равен его значению в C?

189

В следующем фрагменте кода значения указателя и адреса указателя отличаются, как и ожидалось.

Но значения массива и адреса не имеют!

Как это может быть?

Вывод

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}
Александр
источник
Из comp.lang.c FAQ: - [Так что же подразумевается под `` эквивалентностью указателей и массивов '' в C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Поскольку ссылки на массивы распадаются на указатели, если arr является массивом, в чем разница между arr и & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) Или прочитайте весь раздел « Массивы и указатели ».
Джеймсдлин
3
Я добавил ответ с диаграммой на этот вопрос два года назад. Что sizeof(&array)возвращает?
Грижеш Чаухан,

Ответы:

214

Имя массива обычно вычисляется в адрес первого элемента массива, так arrayи &arrayимеет одинаковое значение (но различные типов, так array+1и &array+1будет не быть равными , если массив больше , чем 1 элемент длиной).

Есть два исключения к этому: когда имя массива является операндом sizeofили унарным &(адресом), имя относится к самому объекту массива. Таким образом, sizeof arrayвы получите размер в байтах всего массива, а не размер указателя.

Для массива, определенного как T array[size], он будет иметь тип T *. Когда / если вы увеличиваете его, вы переходите к следующему элементу в массиве.

&arrayвычисляется по тому же адресу, но с учетом того же определения создает указатель типа T(*)[size]- т. е. это указатель на массив, а не на один элемент. Если вы увеличите этот указатель, он добавит размер всего массива, а не размер одного элемента. Например, с таким кодом:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Мы можем ожидать, что второй указатель будет на 16 больше первого (потому что это массив из 16 символов). Поскольку% p обычно преобразует указатели в шестнадцатеричное, это может выглядеть примерно так:

0x12341000    0x12341010
Джерри Гроб
источник
3
@Alexandre: &arrayуказатель на первый элемент массива, где as arrayссылается на весь массив. Принципиальное различие также можно наблюдать, сравнивая sizeof(array), с sizeof(&array). Однако обратите внимание, что если вы передаете arrayв качестве аргумента функции, &arrayфактически передается только. Вы не можете передать массив по значению, если он не инкапсулирован в struct.
Клиффорд
14
@Clifford: если вы передаете массив функции, он так эффективно &array[0]передается по указателю на свой первый элемент , а не &arrayпо указателю на массив. Это может быть придиркой, но я думаю, что это важно прояснить; Компиляторы будут предупреждать, если функция имеет прототип, который соответствует типу переданного указателя.
CB Bailey
2
@Jerry Coffin Например, int * p = & a, если я хочу адрес памяти указателя int p, я могу сделать & p. Так как & массив преобразуется в адрес всего массива (который начинается с адреса первого элемента). Тогда как мне найти адрес памяти указателя массива (который хранит адрес первого элемента в массиве)? Это должно быть где-то в памяти, верно?
Джон Ли
2
@JohnLee: Нет, указатель на массив не должен быть где-либо в памяти. Если вы создаете указатель, вы можете взять его адрес: int *p = array; int **pp = &p;.
Джерри Коффин
3
@ Clifford первый комментарий неправильный, почему все еще держите это? Я думаю, что это может привести к недоразумению для тех, кто не читает следующий ответ (@Charles).
Рик
30

Это потому, что имя массива ( my_array) отличается от указателя на массив. Это псевдоним адреса массива, а его адрес определяется как адрес самого массива.

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

Я писал об этой теме здесь - пожалуйста, посмотрите.

Эли Бендерский
источник
Разве & my_array не должна быть недопустимой операцией, поскольку значение my_array не находится в стеке, а только my_array [0 ... length]? Тогда все это имело бы смысл ...
Александр
@Alexandre: Я не уверен, почему это разрешено, на самом деле.
Эли Бендерский
Вы можете взять адрес любой переменной (если она не помечена register) независимо от продолжительности хранения: статической, динамической или автоматической.
CB Bailey
my_arrayсам находится в стеке, потому что my_array это весь массив.
Кафе
3
my_arrayЕсли объект не является оператором &или sizeof, вычисляется указатель на его первый элемент (т. е. &my_array[0]), но он my_arrayсам не является указателем ( my_arrayпо-прежнему является массивом). Этот указатель - просто эфемерное значение (например, данный int a;, он похож a + 1) - концептуально, по крайней мере, он «вычисляется по мере необходимости». Реальное «значение» my_array- это содержимое всего массива - просто закрепление этого значения в C похоже на попытку уловить туман в банке.
Кафе
28

В C, когда вы используете имя массива в выражении (включая передачу его в функцию), если только оно не является операндом &оператора address-of ( ) или sizeofоператора, оно распадается на указатель на свой первый элемент.

То есть в большинстве контекстов arrayэквивалентно &array[0]как по типу, так и по значению.

В вашем примере my_arrayимеет тип, char[100]который уменьшается до a, char*когда вы передаете его в printf.

&my_arrayимеет тип char (*)[100](указатель на массив из 100 char). Поскольку это операнд &, это один из случаев, когда my_arrayне сразу затухает указатель на его первый элемент.

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

pointer_to_arrayимеет тип char *- инициализируется, чтобы указывать на первый элемент массива, как это происходит my_arrayв выражении инициализатора, - и &pointer_to_array имеет тип char **(указатель на указатель на a char).

Из них: my_array(после распада на char*), &my_arrayи pointer_to_arrayвсе они указывают непосредственно на массив или на первый элемент массива и поэтому имеют одинаковое значение адреса.

CB Bailey
источник
3

Причину my_arrayи &my_arrayрезультат в одном и том же адресе можно легко понять, если взглянуть на структуру памяти массива.

Допустим, у вас есть массив из 10 символов (вместо 100 в вашем коде).

char my_array[10];

Память для my_arrayвыглядит примерно так:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

В C / C ++ массив распадается на указатель на первый элемент в выражении, таком как

printf("my_array = %p\n", my_array);

Если вы посмотрите, где находится первый элемент массива, вы увидите, что его адрес совпадает с адресом массива:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
Р Саху
источник
3

В языке программирования B, который был непосредственным предшественником C, указатели и целые числа были свободно взаимозаменяемыми. Система будет вести себя так, как будто вся память представляет собой гигантский массив. С каждым именем переменной связан либо глобальный, либо относящийся к стеку адрес, для каждого имени переменной единственное, что должен отслеживать компилятор, это глобальная или локальная переменная и его адрес относительно первой глобальной или локальной переменной. переменная.

Учитывая глобальное заявление , как i;[не было никакой необходимости указывать тип, так как все было целое / указатель] будет обрабатываться компилятором как: address_of_i = next_global++; memory[address_of_i] = 0;и заявление , как i++будет обрабатываться как: memory[address_of_i] = memory[address_of_i]+1;.

Декларация вроде arr[10];бы будет обработана как address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Обратите внимание, что как только это объявление было обработано, компилятор мог сразу забыть о том, arrчто он является массивом . Заявление вроде arr[i]=6;бы будет обработано как memory[memory[address_of_a] + memory[address_of_i]] = 6;. Компилятору было бы все равно, arrпредставлены ли массив и iцелое число, или наоборот. Действительно, было бы все равно, если бы они были массивами или обоими целыми числами; он с удовольствием сгенерирует код, как описано, независимо от того, будет ли полученное поведение полезным.

Одна из целей языка программирования C состояла в том, чтобы быть в значительной степени совместимым с B. В B имя массива [называемое "вектором" в терминологии B] идентифицировало переменную, содержащую указатель, который первоначально был назначен для указания на к первому элементу выделения заданного размера, поэтому, если это имя появилось в списке аргументов для функции, функция получит указатель на вектор. Несмотря на то, что C добавил «реальные» типы массивов, чье имя было жестко связано с адресом выделения, а не с переменной-указателем, которая первоначально указывала бы на выделение, наличие массивов, разлагаемых на созданный указателями код, объявивший массив типа C, ведет себя идентично в код B, который объявил вектор, а затем никогда не изменял переменную, содержащую ее адрес.

Supercat
источник
1

На самом деле, &myarrayи myarrayоба являются базовым адресом.

Если вы хотите увидеть разницу вместо использования

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

использование

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
Рави Бисла
источник