1

Тема: Ім'я масиву - це що?

Вітаю. Намагаюся зрозуміти, що таке ім'я масиву в С++. Я вже вроді не зовсім новачок в С++, але деякі моменти вроді зрозумілі, а вроді і не зовсім. Власне погугливши про це в інтернеті, як я зрозумів це таки вказівник, саме тому до імені масиву може бути застосована арифметика вказівників.
Але раз це вказівник, отже він десь може знаходитися в пам'яті та мати свій адрес? А раз така комірка пам'яті є, то, по ідеї, можна дізнатися її адресу так - &arr? На жаль такий трюк не працює і виходить, що &arr == arr. Для мене цей момент не зрозумілий. Чи ім'я масиву це таки посилання, раз не виділяється нічого?

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int* parr = arr; // parr вказує на перший елемент arr, якщо arr (ім'я масиву) це вказівник,
                 // то чому parr вказує на arr[0]? отже arr це посилання?
std::cout << "arr = " << arr + 0 << " parr = " << parr + 0;

2

Re: Ім'я масиву - це що?

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

Подякували: mimik, leofun01, bebyk3

3

Re: Ім'я масиву - це що?

koala написав:

(зараз прийде wander і буде довго лаятися і цитувати стандарт)

Та ні, не буду. Тут нема дуже що цитувати :) Плюс, це займає багато часу.
Ім'я масиву - це дійсно ім'я масиву (об'єкта). Не знаю чому ТС вирішив, що має бути якась додаткова комірка пам'яті.

Подякували: mimik1

4

Re: Ім'я масиву - це що?

Так, вже теж нагуглив, що ім'я масиву - це ім'я масиву. Але що це тоді таке? І як він перетворюється на вказівник тоді?

5

Re: Ім'я масиву - це що?

mimik написав:

Так, вже теж нагуглив, що ім'я масиву - це ім'я масиву. Але що це тоді таке? І як він перетворюється на вказівник тоді?

Псевдокод.

// Представимо масив з 3 елементів (int [3]) у вигляді деякої структури для розуміння.
struct int_array_sizeof_3 {
    int _0;
    int _1;
    int _2;

    operator int*() {
        return &_1;
    }
};

int main() {
    int_array_sizeof_3 arr; // або int arr[3];
    std::cout << &arr << ' ' << arr;
}
Подякували: mimik1

6

Re: Ім'я масиву - це що?

mimik написав:

як він перетворюється на вказівник тоді?

Що значить "як"? За правилами мови. Коли ви пишете

int a = 'A';

У вас же не викликає подиву таке перетворення - char на int? То в чому проблема в автоматичному перетворенні int [10] на int* за певними правилами? Просто компілятор виконує таку роботу, от і все.

7

Re: Ім'я масиву - це що?

koala написав:
mimik написав:

як він перетворюється на вказівник тоді?

Що значить "як"? За правилами мови. Коли ви пишете

int a = 'A';

У вас же не викликає подиву таке перетворення - char на int? То в чому проблема в автоматичному перетворенні int [10] на int* за певними правилами? Просто компілятор виконує таку роботу, от і все.

Перетворення char на int не викликає такого подиву, можливо, хоча б тому, що ім'я змінної не так часто зустрічається, на мою думку. А от "ім'я масиву" доволі часто згадується у темах в книжках чи статтях де так чи інакше намагаються пояснити роботу масивів в С++. Тому мені здається, що це не дивно чому виникають такі питання, адже навіть коли я шукав відповідь на своє питання в інтернеті, то натрапляв на приблизно такі ж теми (в тому числі і на stackoverflow), де навіть гуру з репутацією могли давати різні відповіді. Тому це трохи заплутує, може скластися враження ніби ім'я масиву - це щось окреме.

8 Востаннє редагувалося ReAl (04.12.2020 23:05:11)

Re: Ім'я масиву - це що?

koala написав:

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

Я теж міг би довго лаятися і цитувати, але мені вже набридло.
Але позанудствую — крім sizeof та &

Подякували: koala, leofun012

9 Востаннє редагувалося ReAl (04.12.2020 23:30:28)

Re: Ім'я масиву - це що?

mimik написав:

Тому це трохи заплутує, може скластися враження ніби ім'я масиву - це щось окреме.

Ім'я функції — теж.
Про всяк випадок — у виразі

    foo();

Ім'я функції foo спочатку автоматично приводиться до вказівника на функцію відповідного типу, а вже до цього вказівника прикладається дія (), що загалом схоже на перетворення імені масиву у виразі array[n] (спочатку автоматичне приведення імені масиву до вказівника на перший елемент, а потім до цього вказваника прикладається дія []).
При цьому так само як для масивів — для &foo це автоматичне приведення не робиться (а sizeof до function designator не тулиться). Але foo і &foo означають одне й те ж саме, а з *foo взагалі весело, воно встигає побути розіменованим зовсім недовго і моментально знову автоматично приводиться до вказівника на функцію, завдяки чому foo, *foo і навіть ***foo це одне й те ж саме (якщо не забувати про пріоритети операцій і ставити дужки як треба).

У нас тут в C весело :-)

Подякували: mimik1

10

Re: Ім'я масиву - це що?

Є поширений термін "об'єкт першого класу" в сенсі "з яким можна робити що завгодно, що є в мові". Ну так от, в C масиви і функції - не першого класу, от і все.
https://uk.wikipedia.org/wiki/Об'єкт_першого_класу

Подякували: mimik1

11 Востаннє редагувалося wander (05.12.2020 00:46:49)

Re: Ім'я масиву - це що?

ReAl написав:

Але позанудствую — крім sizeof та &

Ну ок, отже, окрім:

- Коли вираз є (g)lvalue:
    - & вже згадали;
    - static_cast to reference type;
    - reinterpret_cast to reference type;
    - const_cast to reference type;
- При прив''язці до посилання того ж типу масиву;
- При сайд-ефектах, вони ж discarded-value(s);
- Built-in comma operator;
- При decltype;
- При deducing the typename of template.

Полюбе когось забув :) Але більшість думаю згадав)

Подякували: koala, mimik, leofun013

12

Re: Ім'я масиву - це що?

Стоп, а чому & не виконує перетворення масиву у вказівник? Якщо я не помиляюся то &arr == &arr[0]?

13 Востаннє редагувалося ReAl (05.12.2020 03:51:36)

Re: Ім'я масиву - це що?

mimik написав:

Стоп, а чому & не виконує перетворення масиву у вказівник? Якщо я не помиляюся то &arr == &arr[0]?

Під оператором & не виконується автоматичного приведення «імені» масиву до вказівника на його перший елемент («масив у вказівник» не перетворюється взагалі).

int arr[5];

// При інкременті p1 зміщуватиметься і вказуватиме на наступний int,
// його числове значення збільшиться на sizeof(int)
int *p1 = &arr[0];
// або, що еквівалентно
int *p2 = arr;
// але &arr -- вказівник на масив, на весь масив.
// При інкременті parr зміщуватиметься і вказуватиме на наступний 5-елементний масив int,
// його числове значення збільшиться на sizeof(arr) == sizeof(int[5])
int (*parr)[5] = &arr;
// що зовсім не те, що масив вказівників
int *arrp[5] = { arr, arr+1, &arr[2], &arr[3], arr+4};

// А тут  warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
int *p3 = &arr;

(void*) &arr == (void*) &arr[0] але самі вони мають різний тип.
Тому
(void*)(&arr + 1) != (void*)(&arr[0] + 1)

https://onlinegdb.com/KSjaRFvI4

Подякували: koala, leofun012

14

Re: Ім'я масиву - це що?

mimik написав:

Стоп, а чому & не виконує перетворення масиву у вказівник? Якщо я не помиляюся то &arr == &arr[0]?

Подивіться на мій пост вище.
1) Чи став arr посиланням чи вказівником? - Ні, не став. Як був об'єктом типу int_array_sizeof_3, так і залишився.
2) Чому arr та &arr дають один і той же адрес? - Об'єкт arr містить об'єкт _0 першим і тому їхні адреси співпадають.

https://rextester.com/MEXZ10961
Тобто, до arr буде викликано operator int*, який поверне вказівник на _0. В свою чергу &arr поверне вказівник на об'єкт arr (aka 'int_array_sizeof_3*'), але оскільки _0 є першим, то ми отримаємо саме його адрес. Виходить ми порівнюємо (void*)&_0 == (void*)&_0, що очевидно одне і те ж. Це цілком well-defined поведінка, адже layout цих змінних (arr і _0) співпадає. З масивами, в принципі, все те саме (за вийнятком того, що стандарт цього не гарантує :) ). Тому і &arr == &arr[0] дає true.

З типами ще простіше. &arr - дасть вказівник на цілий масив (aka 'int[3]*'), як і &arr з мого прикладу вище давав вказівник на свою структуру (aka 'int_array_sizeof_3*'). А &arr[0] дасть просто вказівник на int*.

int arr[3];
using T = int[3];
T* parr = &arr; // T* = int[3]*
Подякували: leofun01, mimik2

15

Re: Ім'я масиву - це що?

Я б сказав, ім'я масиву можна умовно розглядати як вказівникову константу. З якою можливі всі незмінюючі вказівникові операції — тобто, лише такі, де значення вказівника лишається тим же, що й було (сам вказуваний об'єкт (тобто, власне масив) змінювати при цьому можна). З точки зору букви стандарту це, ймовірно, незовсім так (а в дечому навіть зовсім нетак — наприклад, якщо це локальний масив, розміщуваний у стеку, то адреса його не може бути визначеною наперед — тобто, це не константа в звичайному сенсі).

Подякували: mimik1

16

Re: Ім'я масиву - це що?

Всім дякую за відповіді, здається, все більш-менш виглядає зрозумілим та закономірним.
Також, було цікаво почитати про те, як поводяться функції та вказівники на них.
А така ж поведінка з ***foo є і в С++?
І ще хотів запитатися, звідки вся ця інформація про те, що коли масив не перетворюється у вказівник. Наприклад, той же &, тобто я розумію тепер, чому & працює саме так, а не інакше. Але звідки про це можна дізнатися? В книжках такого нема (принаймні в тих, що є в мене).

17

Re: Ім'я масиву - це що?

mimik написав:

А така ж поведінка з ***foo є і в С++?

Ну, так. Там лише деякі формальні відмінності існують. Типу в С++ ім'я функції не перетворюється у вказівник, щоб потім виконати () виклик.

mimik написав:

І ще хотів запитатися, звідки вся ця інформація про те, що коли масив не перетворюється у вказівник. Наприклад, той же &, тобто я розумію тепер, чому & працює саме так, а не інакше. Але звідки про це можна дізнатися?

Зі стандарту С++.

Подякували: mimik, ReAl2

18

Re: Ім'я масиву - це що?

mimik написав:

І ще хотів запитатися, звідки вся ця інформація про те, що коли масив не перетворюється у вказівник. Наприклад, той же &, тобто я розумію тепер, чому & працює саме так, а не інакше. Але звідки про це можна дізнатися? В книжках такого нема (принаймні в тих, що є в мене).

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