C вказівник на масив / масив покажчиків розбіжності


463

Яка різниця між наступними деклараціями:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Яке загальне правило для розуміння більш складних декларацій?


54
Ось велика стаття про читання складних декларацій в C: unixwiz.net/techtips/reading-cdecl.html
Jesper

@jesper На жаль, constта volatileкласифікатори, які є важливими та складними, відсутні у цій статті.
не-користувач

@ не-користувач це не стосується цього питання. Ваш коментар не відповідає. Будь ласка, утримайтеся.
користувач64742

Відповіді:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

Третя така ж, як і перша.

Загальне правило - пріоритет оператора . Це може стати навіть набагато складнішим, оскільки вказівники функцій потрапляють у зображення.


4
Отже, для 32-бітових систем: int * arr [8]; / * 8x4 байтів, виділених для кожного вказівника / int (* arr) [8]; / Виділено 4 байти, лише вказівник * /
Джордж

10
Ні. int * arr [8]: 8x4 байтів, виділених загалом , 4 байти для кожного вказівника. int (* arr) [8] вірно, 4 байти.
Мехрдад Афшарі

2
Я повинен був перечитати те, що написав. Я мав на увазі 4 для кожного вказівника. Дякую за допомогу!
Джордж

4
Причина того, що перша така ж, як і остання, полягає в тому, що завжди дозволено обертати дужки навколо деклараторів. P [N] - декларатор масиву. P (....) - декларатор функції, а * P - декларатор покажчика. Отже, все в наступному - це те саме, що і без жодних дужок (крім однієї з функцій '"()": int (((* p))); void ((g (void)))); int * (a [1]); void (* (p ())).
Йоганнес Шауб - litb

2
Молодці у вашому поясненні. Для поглибленого посилання на перевагу та асоціативність операторів див. Сторінку 53 мови програмування C (Бразилія Керніган та Денніс Річі). Оператори ( ) [ ] асоціюються зліва направо та мають вищий пріоритет, ніж *так, int* arr[8]як читати як масив розміром 8, де кожен елемент вказує на int та int (*arr)[8]як вказівник на масив розміром 8, який містить цілі числа
Mushy

267

Використовуйте програму cdecl , як пропонує K&R.

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Це працює і іншим способом.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii У більшості дистрибутивів Linux має бути пакет. Ви також можете створити свій власний двійковий файл.
sigjuice

Ах вибачте за те, що тут не згадуєте, macOS. побачать, якщо вони доступні, інакше веб-сайт теж добре. ^ дякую, що повідомили мені про це. Не соромтеся позначити NLN.
ankii

2
@ankii Ви можете встановити з Homebrew (а може бути, MacPorts?). Якщо це не на ваш смак, тривіально створити своє власне з посилання Github у верхньому правому куті cdecl.org (я щойно створив його на macOS Mojave). Тоді просто скопіюйте файл cdecl у свій PATH. Я рекомендую $ PATH / bin, тому що немає необхідності залучати root до чогось такого простого, як це.
sigjuice

Ой не читав маленький абзац про встановлення в readme. лише деякі команди та прапори для обробки залежностей. Встановлюється за допомогою brew. :)
ankii

1
Коли я вперше прочитав це, я подумав: "Я ніколи не зійду до цього рівня". Наступного дня я його завантажив.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

126

Я не знаю, чи має вона офіційне найменування, але я називаю це Праворуч Справедливий (TM).

Почніть з змінної, потім направо, і вліво, і вправо ... і так далі.

int* arr1[8];

arr1 це масив з 8 покажчиків на цілі числа.

int (*arr2)[8];

arr2 є вказівником (блок дужок справа-зліва) на масив з 8 цілих чисел.

int *(arr3[8]);

arr3 це масив з 8 покажчиків на цілі числа.

Це повинно допомогти вам скласти декларації.


19
Я чув, як це згадується під назвою "Правила спіралі", яке можна знайти тут .
чотирирічний

6
@InkBlend: Правило спіралі відрізняється від правило-лівого правила . Перший виходить з ладу у випадках, int *a[][10]коли останній досягає успіху.
legends2k

1
@dogeen Я думав, що цей термін має щось спільне з Bjarne Stroustrup :)
Anirudh Ramanathan

1
Як говорили InkBlend та legends2k, це правило спіралі, яке є більш складним і працює не у всіх випадках, тому немає ніяких причин використовувати його.
kotlomoy

Не забувайте асоціативність ( ) [ ]ліворуч праворуч і праворуч ліворуч* &
Мюші

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

Чи не повинен бути 3-й: a - це масив покажчиків на цілий масив розміром 8? Я маю на увазі, що кожен із цілих масивів буде розміром 8 правильно?
Rushil Paul

2
@Rushil: ні, останній індекс ( [5]) являє собою внутрішній вимір. Це означає, що (*a[8])це перший вимір, і, таким чином, зовнішнє зображення масиву. На що a вказує кожен елемент , це інший цілий масив розміром 5.
zeboidlund

Дякую за третю. Я шукаю, як записати масив покажчиків на масив.
Декін

15

Відповідь на останні два можна також вивести із золотого правила в С:

Декларація слід використовувати.

int (*arr2)[8];

Що станеться, якщо знешкодження arr2? Ви отримуєте масив з 8 цілих чисел.

int *(arr3[8]);

Що станеться, якщо взяти елемент arr3? Ви отримуєте вказівник на ціле число.

Це також допомагає при роботі з покажчиками на функції. Для прикладу sigjuice:

float *(*x)(void )

Що трапляється, коли ви зневажаєте x? Ви отримуєте функцію, яку можна викликати без аргументів. Що відбувається, коли ви його називаєте? Він поверне вказівник на a float.

Проте пріоритет оператора завжди складний. Однак використання дужок може насправді також заплутати, оскільки декларація слід використовувати. Принаймні, мені інтуїтивно arr2виглядає як масив з 8 вказівників на ints, але насправді навпаки. Просто потрібно трохи звикнути. Причина достатня, щоб завжди додавати коментар до цих декларацій, якщо ви запитаєте мене :)

редагувати: приклад

До речі, я просто натрапив на таку ситуацію: функція, яка має статичну матрицю і яка використовує арифметику вказівника, щоб побачити, чи вказівник рядка не виходить за межі. Приклад:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Вихід:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Зауважте, що значення межі ніколи не змінюється, тому компілятор може оптимізувати це. Це відрізняється від того, що ви, можливо, спочатку хочете використовувати:: const int (*border)[3]що оголошує кордон як вказівник на масив з 3 цілих чисел, які не змінять значення, поки існує змінна. Однак цей покажчик може бути вказаний на будь-який інший такий масив у будь-який час. Натомість ми хочемо такої поведінки для аргументу (оскільки ця функція не змінює жодного з цих цілих чисел). Декларація слід використовувати.

(ps: сміливо покращуйте цей зразок!)



3

Як правило, праві одинарних оператори (як [], (), і т.д.) превалювати над лівими з них. Отже, int *(*ptr)()[];був би вказівник, який вказує на функцію, яка повертає масив покажчиків на int (отримайте потрібних операторів, як тільки зможете, як ви вийдете з дужок)


Це правда, але це також нелегально. Ви не можете мати функцію, яка повертає масив. Я спробував і отримав таке: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];під GCC 8 з$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito

1
Причиною компілятора давати цю помилку є те, що вона інтерпретує функцію як повертаючий масив, як констатується правильна інтерпретація правила пріоритету. Вона є незаконною як декларація, але юридична декларація int *(*ptr)();дозволяє вираз, як p()[3](або (*p)()[3]), використовувати згодом.
Луїс Колорадо

Добре, якщо я це розумію, ви говорите про створення функції, яка повертає вказівник на перший елемент масиву (а не на сам масив), а пізніше використовуєте цю функцію так, ніби вона повертає масив? Цікава ідея. Я спробую. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }і називати це так: foo(arr)[4];що має містити arr[2][4], правда?
Cacahuete Frito

так ... але ви також мали рацію, і декларація була незаконною. :)
Луїс Колорадо

2

Я думаю, ми можемо використовувати просте правило ..

example int * (*ptr)()[];
start from ptr 

" ptr- вказівник на" іти вправо .. його ")" тепер перейти вліво "(" вийти вправо "()" так "до функції, яка не бере аргументів" йти вліво "і повертає вказівник" йти " праворуч "на масив" перейти ліворуч "на цілі числа"


Я би це трохи вдосконалив: "ptr - це ім'я, яке посилається на" ідіть праворуч ... це ), тепер ідіть вліво ... це *"вказівник на" йти праворуч ... це ), тепер ідіть вліво ... це (вийти, йти прямо ()так «на функцію , яка приймає ніяких аргументів» не так ... []«і повертає масив» йти прямо ;кінець, тому йдіть наліво ... *«покажчики» наліво ... int«цілих»
Cacahuete Frito


2

Ось як я трактую це:

int *something[n];

Зауважте про пріоритет: оператор індексів масиву ( []) має більш високий пріоритет, ніж оператор перенавантаження ( *).

Отже, тут ми застосуємо []раніше *, зробивши заяву рівнозначним:

int *(something[i]);

Зверніть увагу на те, як декларація має сенс: int numозначає, що numєint , int *ptrабо int (*ptr)означає, що (значення at ptr) є an int, що робить ptrвказівник на int.

Це можна прочитати як, (значення (значення в i-му індексі чогось)) є цілим числом. Отже, (значення в i-му індексі чогось) - це (ціле число вказівника), яке робить щось масивом цілочисельних покажчиків.

У другому

int (*something)[n];

Щоб мати сенс з цього твердження, ви повинні бути знайомі з цим фактом:

Примітка щодо представлення масиву вказівника: somethingElse[i]еквівалентно*(somethingElse + i)

Отже, замінюючи somethingElseна (*something), ми отримуємо *(*something + i), що є цілим числом відповідно до декларації. Отже, (*something)дано нам масив, який робить щось рівнозначне (вказівник на масив) .


0

Я думаю, друга декларація для багатьох бентежить. Ось простий спосіб зрозуміти це.

Дозволяє мати масив цілих чисел, тобто int B[8].

Маємо також змінну A, яка вказує на B. Тепер, значення A є B, тобто (*A) == B. Звідси A вказує на масив цілих чисел. У вашому запитанні arr схожий на A.

Аналогічно, в int* (*C) [8], C - вказівник на масив покажчиків на ціле число.


0
int *arr1[5]

У цій декларації arr1є масив з 5 покажчиків на цілі числа. Причина: квадратні дужки мають більшу перевагу над * (оператор відміни). І в цьому типі фіксується кількість рядків (5 тут), але кількість стовпців є змінною.

int (*arr2)[5]

У цій декларації arr2є вказівник на цілий масив з 5 елементів. Причина: Тут дужки () мають більший пріоритет, ніж []. І в цьому типі кількість рядків є змінною, але кількість стовпців фіксована (5 тут).


-7

У покажчику на ціле число, якщо покажчик збільшується, він переходить до наступного цілого числа.

у масиві вказівника, якщо покажчик збільшується, він переходить до наступного масиву


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