Чи називається масив вказівником?


203

Чи ім'я масиву вказує на С? Якщо ні, то яка різниця між назвою масиву та змінною вказівника?


4
Ні. Але масив такий самий & масив [0]

36
@pst: &array[0]дає вказівник, а не масив;)
jalf

28
@Nava (і pst): масив та & масив [0] насправді не однакові. Справа в точці: sizeof (масив) і sizeof (& масив [0]) дають різні результати.
Томас Падрон-Маккарті

1
@ Томас погоджується, але з точки зору покажчиків, коли ви відміняєте масив та & масив [0], вони створюють однакове значення масиву [0]. Ніхто не означав, що ці два вказівники однакові, але в цьому конкретному випадку (вказуючи на перший елемент) ви можете використовувати ім'я масиву.
Nava Carmon

1
Вони також можуть допомогти вам у розумінні: stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Діна

Відповіді:


255

Масив - це масив, а вказівник - вказівник, але в більшості випадків імена масивів перетворюються на покажчики. Термін, який часто використовується, полягає в тому, що вони розпадаються на покажчики.

Ось масив:

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означає сам масив.


5
Аналогічне автоматичне перетворення застосовується до функціональних покажчиків - і те, functionpointer()і (*functionpointer)()означають те саме, що не дивно.
Карл Норум

3
Він не запитував, чи масиви та покажчики однакові, але якщо ім'я масиву - вказівник
Рікардо Аморес

32
Ім'я масиву не є вказівником. Це ідентифікатор для змінної типу масиву, яка має неявне перетворення в покажчик типу елемента.
Павло Мінаєв

29
Крім того, sizeof()інший контекст, в якому відсутній розпад масиву-> покажчик, є оператором &- у вашому прикладі вище &aбуде вказівник на масив 7 int, а не вказівник на єдиний int; тобто буде його тип int(*)[7], у який неявно не можна конвертувати int*. Таким чином, функції можуть фактично приймати покажчики на масиви певного розміру та застосовувати обмеження через систему типів.
Павло Мінаєв

3
@ onmyway133, знайдіть тут коротке пояснення та подальші цитати.
Карл Норум

36

Коли масив використовується як значення, його ім'я представляє адресу першого елемента.
Коли масив не використовується як значення, його ім'я представляє весь масив.

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 */

20

Якщо вираз типу масиву (наприклад, ім'я масиву) з'являється у більшій виразі, і це не операнд ні операторів, &ні 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), або потрібно передати кількість елементів як окремий параметр.


  1. Яке * може вплинути на інтерпретацію значення адреси - залежить від машини.

Досить довго шукали цю відповідь. Дякую! І якщо ви знаєте, чи можете ви трохи далі сказати, що таке вираз масиву. Якщо я використовую sizeof, чи вважаю я розмір лише елементів масиву? Тоді масив "head" також займає простір з інформацією про довжину та покажчик (а це означає, що він займає більше місця, ніж звичайний вказівник)?
Андрій Дмитрук

І ще одне. Масив довжиною 5 має тип int [5]. Отже, звідки ми знаємо довжину, коли називаємо sizeof (масив) - від її типу? А це означає, що масиви різної довжини схожі на різні типи констант?
Андрій Дмитрук

@AndriyDmytruk: sizeofє оператором, і він оцінює число байтів в операнді (або вираз, що позначає об'єкт, або ім'я типу в дужках). Отже, для масиву sizeofоцінюється кількість елементів, помножена на кількість байтів в одному елементі. Якщо intширина 4 байти, то 5-елементний масив intзаймає 20 байт.
Джон Боде

Хіба оператор [ ]теж не особливий? Наприклад, int a[2][3];тоді для x = a[1][2];, хоча він може бути переписаний як x = *( *(a+1) + 2 );, тут aне перетворюється на тип вказівника int*(хоча, якщо aце аргумент функції, його слід перетворити int*).
Стен

2
@Stan: Вираз 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. Як це відображається на машинному коді, повністю залежить від компілятора.
Джон Боде

5

Масив, оголошений так

int a[10];

виділяє пам'ять на 10 intс. Ви не можете змінювати, aале ви можете робити арифметику вказівника за допомогою a.

Такий вказівник виділяє пам'ять лише для вказівника p:

int *p;

Він не виділяє жодних ints. Ви можете змінити його:

p = a;

і використовувати підписки на масив, як можна:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect

2
Масиви не завжди виділяються на стеку. Yhat - це детальна інформація про реалізацію, яка буде відрізнятися від компілятора до компілятора. У більшості випадків статичні або глобальні масиви будуть виділятися з іншої області пам'яті, ніж стек. Масиви типів const можуть бути виділені з ще однієї області пам’яті
Mark Bessey

1
Я думаю, що Grumdrig мав на увазі сказати "виділяє 10 intсекунд з автоматичною тривалістю зберігання".
Гонки легкості на орбіті

4

Ім'я масиву саме по собі дає місце в пам'яті, тому ви можете обробляти ім'я масиву як вказівник:

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]);

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вказівника.


1
Msgstr "Складається штраф (з 2 попередженнями)". Це не добре. Якщо ви скажете gcc скласти його як належний стандарт C, додавши-std=c11 -pedantic-errors , ви отримаєте помилку компілятора для написання недійсного коду C. Причина тому, що ви намагаєтеся призначити int (*)[3]змінну int**, яка є двома типами, які абсолютно нічого спільного між собою не мають. Отже, що цей приклад повинен довести, я поняття не маю.
Лундін

Дякую Лундін за ваш коментар. Ви знаєте, що існує багато стандартів. Я намагався уточнити, що я мав на увазі під редакцією. int **Тип не крапка там, слід краще використовувати void *для цього.
Пало

-3

Ім'я масиву поводиться як вказівник і вказує на перший елемент масиву. Приклад:

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

-4

Масив - це сукупність послідовних та суміжних елементів у пам'яті. Ім'я масиву 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. Дано: int a [10]; int * p = a; sizeof (p) та sizeof (a) не однакові.
Марк Бессі

1
Є й інші відмінності. Загалом, найкраще дотримуватися термінології, що використовується стандартом C, який конкретно називає це "перетворенням". Цитата: "За винятком випадків, коли це операнд оператора sizeof або unary & operator, або це рядковий літерал, який використовується для ініціалізації масиву, вираз, який має тип '' масив типу '', перетворюється на вираз з типом ' 'вказівник на тип' ', який вказує на початковий елемент об'єкта масиву і не є значенням. Якщо об'єкт масиву має клас зберігання регістру, поведінка не визначена. "
Павло Мінаєв

-5

Назва масиву - це адреса 1-го елемента масиву. Отже, ім'я масиву так - це покажчик const.

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