Чи ім'я масиву вказує на С? Якщо ні, то яка різниця між назвою масиву та змінною вказівника?
&array[0]
дає вказівник, а не масив;)
Чи ім'я масиву вказує на С? Якщо ні, то яка різниця між назвою масиву та змінною вказівника?
&array[0]
дає вказівник, а не масив;)
Відповіді:
Масив - це масив, а вказівник - вказівник, але в більшості випадків імена масивів перетворюються на покажчики. Термін, який часто використовується, полягає в тому, що вони розпадаються на покажчики.
Ось масив:
int a[7];
a
містить простір для семи цілих чисел, і ви можете поставити значення в одне з них із призначенням, як це:
a[3] = 9;
Ось вказівник:
int *p;
p
не містить пробілів для цілих чисел, але це може вказувати на пробіл для цілого числа. Наприклад, ми можемо встановити його так, щоб воно вказувало на одне з місць масивуa
, наприклад, на перше:
p = &a[0];
Що може бентежити, що ви також можете написати це:
p = a;
Це не копіює вміст масиву a
в покажчикp
(що б це не означало). Натомість ім'я масиву a
перетворюється в покажчик на його перший елемент. Отже, це завдання виконує те саме, що і попереднє.
Тепер ви можете використовувати p
аналогічно до масиву:
p[3] = 17;
Причиною цього є те, що оператор перенаправлення масиву в C, [ ]
визначається за допомогою покажчиків. x[y]
означає: почніть з вказівника x
, виведіть y
елементи вперед після того, на що вказує вказівник, а потім зробіть все, що там є. Використовуючи арифметичний синтаксис покажчика, x[y]
також можна записати як *(x+y)
.
Щоб це працювало з нормальним масивом, таким як наш a
, ім'я a
в a[3]
спершу потрібно перетворити на покажчик (до першого елемента в a
). Потім ми крокуємо 3 елементи вперед і робимо все, що там є. Іншими словами: візьміть елемент у позиції 3 в масиві. (Що є четвертим елементом у масиві, оскільки перший нумерується 0.)
Отже, підсумовуючи, назви масивів у програмі C (у більшості випадків) перетворюються на покажчики. Один виняток - коли ми використовуємо sizeof
оператор у масиві. Якби a
в цьому контексті було перетворено на покажчик, sizeof a
він надав би розмір вказівника, а не фактичного масиву, що було б досить марно, тому в такому випадку a
означає сам масив.
functionpointer()
і (*functionpointer)()
означають те саме, що не дивно.
sizeof()
інший контекст, в якому відсутній розпад масиву-> покажчик, є оператором &
- у вашому прикладі вище &a
буде вказівник на масив 7 int
, а не вказівник на єдиний int
; тобто буде його тип int(*)[7]
, у який неявно не можна конвертувати int*
. Таким чином, функції можуть фактично приймати покажчики на масиви певного розміру та застосовувати обмеження через систему типів.
Коли масив використовується як значення, його ім'я представляє адресу першого елемента.
Коли масив не використовується як значення, його ім'я представляє весь масив.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Якщо вираз типу масиву (наприклад, ім'я масиву) з'являється у більшій виразі, і це не операнд ні операторів, &
ні sizeof
операторів, тоді тип виразу масиву перетворюється з "масиву N-елементів T" в "вказівник на T", а значенням виразу є адреса першого елемента в масиві.
Коротше кажучи, ім'я масиву не є вказівником, але в більшості контекстів воно трактується як би він був покажчиком.
Редагувати
Відповідь на питання в коментарі:
Якщо я використовую sizeof, чи вважаю я розмір лише елементів масиву? Тоді масив "head" також займає простір з інформацією про довжину та покажчик (а це означає, що він займає більше місця, ніж звичайний вказівник)?
Коли ви створюєте масив, єдиний виділений простір - це простір для самих елементів; для окремого вказівника чи будь-яких метаданих не зберігається жодне сховище. Дано
char a[10];
те, що ви отримуєте в пам'яті, це
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
Вираз a
відноситься до всього масиву, але немає ніякого об'єкта a
окремо від самих елементів масиву. Таким чином, sizeof a
дає розмір (у байтах) усього масиву. Вираз &a
дає вам адресу масиву, яка є такою ж, як адреса першого елемента . Різниця між &a
і &a[0]
є типом результату 1 - char (*)[10]
у першому випадку таchar *
у другому.
Де все стає дивнішим, коли ви хочете отримати доступ до окремих елементів - вираз a[i]
визначається як результат *(a + i)
- з урахуванням адресного значення a
, зміщення i
елементів (а не байтів ) з цієї адреси та зменшення результату.
Проблема полягає в тому, що a
це не вказівник чи адреса - це весь об’єкт масиву. Таким чином, правило в C: коли компілятор бачить вираз типу масиву (наприклад a
, який має тип char [10]
) і це вираження не є операндом операторів sizeof
або унарних &
операторів, тип цього виразу перетворюється ("розпадається") на тип вказівника ( char *
), а значенням виразу є адреса першого елемента масиву. Тому вираз a
має той самий тип і значення, що і вираз &a[0]
(і за розширенням, вираз *a
має той самий тип і значення, що і вираз a[0]
).
З був отриманий з більш раннього мови під назвою В, так і в B a
був окремий об'єкт покажчик з елементів масиву a[0]
,a[1]
і т.д. Річі хотів зберегти семантику масиву B, але він не хотів возитися зі зберіганням окремого об'єкта покажчика. Так він його позбувся. Натомість компілятор перетворює вирази масиву в вирази вказівника під час перекладу за необхідності.
Пам'ятайте, що я сказав, що масиви не зберігають жодних метаданих про їх розмір. Як тільки вислів масиву "розпадається" на покажчик, все, що у вас є, - це вказівник на один елемент. Цей елемент може бути першим з послідовності елементів, або він може бути одним об'єктом. Неможливо дізнатися на основі самого вказівника.
Коли ви передаєте вираз масиву функції, вся отримана функція - це вказівник на перший елемент - він поняття не має, наскільки великий масив (саме тому gets
функція була такою загрозою і врешті-решт була видалена з бібліотеки). Щоб функція знала, скільки елементів має масив, потрібно або використовувати дозорне значення (наприклад, термінатор 0 у рядках C), або потрібно передати кількість елементів як окремий параметр.
sizeof
є оператором, і він оцінює число байтів в операнді (або вираз, що позначає об'єкт, або ім'я типу в дужках). Отже, для масиву sizeof
оцінюється кількість елементів, помножена на кількість байтів в одному елементі. Якщо int
ширина 4 байти, то 5-елементний масив int
займає 20 байт.
[ ]
теж не особливий? Наприклад, int a[2][3];
тоді для x = a[1][2];
, хоча він може бути переписаний як x = *( *(a+1) + 2 );
, тут a
не перетворюється на тип вказівника int*
(хоча, якщо a
це аргумент функції, його слід перетворити int*
).
a
має тип int [2][3]
, який "розпадається" на тип int (*)[3]
. Вираз *(a + 1)
має тип int [3]
, який "розпадається" на int *
. Таким чином, *(*(a + 1) + 2)
буде мати тип int
. a
вказує на перший 3-елементний масив int
, a + 1
вказує на другий 3-елементний масив int
, *(a + 1)
- це другий 3-елементний масив int
, *(a + 1) + 2
вказує на третій елемент другого масиву int
, таким *(*(a + 1) + 2)
є третій елемент другого масиву int
. Як це відображається на машинному коді, повністю залежить від компілятора.
Масив, оголошений так
int a[10];
виділяє пам'ять на 10 int
с. Ви не можете змінювати, a
але ви можете робити арифметику вказівника за допомогою a
.
Такий вказівник виділяє пам'ять лише для вказівника p
:
int *p;
Він не виділяє жодних int
s. Ви можете змінити його:
p = a;
і використовувати підписки на масив, як можна:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
секунд з автоматичною тривалістю зберігання".
Ім'я масиву саме по собі дає місце в пам'яті, тому ви можете обробляти ім'я масиву як вказівник:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
І інші чудові речі, які ви можете зробити для вказівника (наприклад, додавання / субстракція зміщення), ви також можете зробити для масиву:
printf("value at memory location %p is %d", a + 1, *(a + 1));
Якщо мова не сприймає мову, якщо C не виставляє масив лише як якийсь "покажчик" (педантично це лише місце в пам'яті. Він не може вказувати на довільне розташування в пам'яті, а також не може контролювати програміст). Нам завжди потрібно це кодувати:
printf("value at memory location %p is %d", &a[1], a[1]);
Я думаю, що цей приклад прояснює проблему:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Він компілює штраф (з 2 попередженнями) в gcc 4.9.2 і друкує наступне:
a == &a: 1
oops :-)
Отже, висновок - ні, масив - це не вказівник, він не зберігається в пам'яті (навіть не лише для читання) як вказівник, навіть незважаючи на те, що він є таким, оскільки ви можете отримати його адресу разом із оператором & . Але - ой, що оператор не працює :-)), в будь-якому випадку, вас попередили:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ відмовляється від будь-яких подібних спроб із помилками під час компіляції.
Редагувати:
Це те, що я мав продемонструвати:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Незважаючи на те, що c
і a
"вказує" на одну пам'ять, ви можете отримати адресу c
вказівника, але ви не можете отримати адресу a
вказівника.
-std=c11 -pedantic-errors
, ви отримаєте помилку компілятора для написання недійсного коду C. Причина тому, що ви намагаєтеся призначити int (*)[3]
змінну int**
, яка є двома типами, які абсолютно нічого спільного між собою не мають. Отже, що цей приклад повинен довести, я поняття не маю.
int **
Тип не крапка там, слід краще використовувати void *
для цього.
Ім'я масиву поводиться як вказівник і вказує на перший елемент масиву. Приклад:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Обидва заяви про друк дають абсолютно однаковий вихід для машини. У моїй системі він дав:
0x7fff6fe40bc0
Масив - це сукупність послідовних та суміжних елементів у пам'яті. Ім'я масиву C - це індекс першого елемента, застосувавши зміщення, ви можете отримати доступ до решти елементів. "Покажчик на перший елемент" дійсно є вказівником на напрямок пам'яті.
Різниця зі змінними вказівника полягає в тому, що ви не можете змінити розташування, на яке вказує ім'я масиву, настільки це схоже на вказівник const (він схожий, не той самий. Див. Коментар Марка). Але також, що вам не потрібно скидати назву масиву, щоб отримати значення, якщо ви використовуєте арифметику вказівника:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
Тож відповідь ніби "так".
Назва масиву - це адреса 1-го елемента масиву. Отже, ім'я масиву так - це покажчик const.