Чому адреса масиву дорівнює його значенню в 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;
}

З FAQ.- comp.lang.c: - [Отже, що означає "еквівалентність покажчиків і масивів" в C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Оскільки посилання масиву розпадаються на покажчики, якщо arr - масив, у чому різниця між arr та & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) Або переходьте прочитати весь розділ Масиви та Покажчики .
jamesdlin

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- вказівник на перший елемент масиву, де arrayпосилається на весь масив. Принципову різницю можна також спостерігати, порівнюючи sizeof(array), до sizeof(&array). Однак зауважте, що якщо ви передаєте arrayяк аргумент функції, &arrayпередається лише насправді. Ви не можете передавати масив за значенням, якщо він не інкапсульований за допомогою a struct.
Кліффорд

14
@Clifford: Якщо ви передаєте масив функції, він занепадає до покажчика на його перший елемент, так ефективно &array[0]переданий, а не &arrayякий би був вказівник на масив. Це може бути ніт-сік, але я думаю, що це важливо уточнити; компілятори попередить, якщо функція має прототип, який відповідає типу переданого вказівника.
CB Bailey

2
@Jerry Coffin Наприклад, int * p = & a, якщо я хочу адресу пам'яті int pointer p, я можу зробити & p. Оскільки & масив перетворюється на адресу всього масиву (який починається з адреси першого елемента). Тоді як я можу знайти адресу пам'яті вказівника масиву (який зберігає адресу першого елемента в масиві)? Це повинно бути десь у пам’яті, правда?
Джон Лі

2
@JohnLee: Ні, ніде в пам’яті не повинно бути вказівника на масив. Якщо ви створюєте покажчик, ви можете взяти його адресу: int *p = array; int **pp = &p;.
Джеррі Труну

3
@Clifford перший коментар невірний, чому все-таки зберігати його? Я думаю, що це може призвести до нерозуміння для тих, хто не читає таку відповідь (@Charles).
Рік

30

Це тому, що ім'я масиву ( my_array) відрізняється від вказівника на масив. Це псевдонім до адреси масиву, а його адреса визначається як адреса самого масиву.

Однак вказівник є звичайною змінною С на стеку. Таким чином, ви можете взяти його адресу і отримати інше значення від адреси, яку він має всередині.

Я писав про цю тему тут - будь ласка, подивіться.


Чи не слід & my_array бути недійсною операцією, оскільки значення my_array не знаходиться в стеці, є лише my_array [0 ... length]? Тоді це все б мало сенс ...
Олександр

@Alexandre: Насправді я не впевнений, чому це дозволено.
Елі Бендерський

Ви можете взяти адресу будь-якої змінної (якщо вона не позначена register) незалежно від її тривалості зберігання: статичної, динамічної або автоматичної.
CB Bailey

my_arrayсама стоїть на стеці, тому що my_array це весь масив.
caf

3
my_array, коли не суб'єкт операторів &або sizeofоператорів, оцінюється вказівником на його перший елемент (тобто &my_array[0]) - але my_arrayсам по собі це не вказівник ( my_arrayвсе ще є масив). Цей покажчик - це лише ефемерне оцінювання (наприклад, дано int a;, це просто так a + 1) - концептуально принаймні воно "розраховується за потребою". Справжня "цінність" my_array- це вміст усього масиву - це саме те, що закріпити це значення на C - це як спробувати зловити туман у банку.
caf

28

У C, коли ви використовуєте ім'я масиву в виразі (включаючи передачу його функції), якщо це не операнд &оператора адреси ( ) або sizeofоператора, він схиляється до вказівника на його перший елемент.

Тобто в більшості контекстів arrayрівносильно&array[0] як за типом, так і за значенням.

У вашому прикладі my_arrayє тип, char[100]який розпадається на achar* коли ви передаєте його до 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всі вказують безпосередньо на масив або на перший елемент масиву і тому мають однакове значення адреси.


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 додав "реальні" типи масивів, ім'я яких жорстко асоціювалося з адресою виділення, а не змінною вказівника, яка спочатку вказувала б на розподіл, маючи масиви розкладатися на покажчики, зроблені кодом, який оголосив масив типу С поводитися однаково до коду B, який оголосив вектор, а потім ніколи не змінював змінну, що містить її адресу.


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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.