Wskaźnik C do tablicy / tablica wskaźników ujednoznacznienia

463

Jaka jest różnica między następującymi deklaracjami:

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

Jaka jest ogólna zasada rozumienia bardziej złożonych deklaracji?

Jerzy
źródło
54
Oto świetny artykuł na temat czytania złożonych deklaracji w C: unixwiz.net/techtips/reading-cdecl.html
jesper
@jesper Niestety w tym artykule brakuje kwalifikatorów consti volatile, które są ważne i trudne.
nie-użytkownik
@ not-a-user nie są istotne dla tego pytania. Twój komentarz nie jest istotny. Powstrzymaj się.
user64742,

Odpowiedzi:

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

Trzeci jest taki sam jak pierwszy.

Ogólną zasadą jest pierwszeństwo operatora . Może stać się jeszcze bardziej skomplikowane, gdy na ekranie pojawią się wskaźniki funkcji.

Mehrdad Afshari
źródło
4
Tak więc dla systemów 32-bitowych: int * arr [8]; / * 8 x 4 przydzielone bajty, dla każdego wskaźnika / int (* arr) [8]; / 4 przydzielone bajty, tylko wskaźnik * /
George
10
Nie. int * arr [8]: Łącznie przydzielono 8 x 4 bajty, 4 bajty na każdy wskaźnik. int (* arr) [8] ma rację, 4 bajty.
Mehrdad Afshari
2
Powinienem był przeczytać to, co napisałem. Miałem na myśli 4 dla każdego wskaźnika. Dzięki za pomoc!
George
4
Powodem, dla którego pierwszy jest taki sam jak ostatni, jest to, że zawsze można owijać nawiasy wokół deklaratorów. P [N] jest deklaratorem tablicy. P (....) jest deklaratorem funkcji, a * P jest deklaratorem wskaźnika. Tak więc wszystko poniżej jest takie samo, jak bez nawiasów (z wyjątkiem jednej z funkcji „” () ”: int (((* p))); void ((g (void))); int * (a [1]); void (* (p ())).
Johannes Schaub - litb
2
Dobra robota w twoim wyjaśnieniu. Szczegółowe informacje na temat pierwszeństwa i asocjatywności operatorów można znaleźć na stronie 53 The C Programming Language (wydanie drugie ANSI C) autorstwa Briana Kernighana i Dennisa Ritchie. Operatory ( ) [ ] kojarzą od lewej do prawej i mają wyższy priorytet niż *odczytane int* arr[8]jako tablica wielkości 8, gdzie każdy element wskazuje na liczbę int (*arr)[8]całkowitą i jako wskaźnik na tablicę wielkości 8, która zawiera liczby całkowite
Mushy
267

Użyj programu cdecl , zgodnie z sugestią 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>

Działa to również w drugą stronę.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
sigjuice
źródło
@ankii Większość dystrybucji Linuksa powinna mieć pakiet. Możesz także zbudować swój własny plik binarny.
sigjuice
ah przepraszam, że nie wspomniałem, macOS tutaj. zobaczą, czy są dostępne, w przeciwnym razie strona też będzie w porządku. ^^ dzięki za poinformowanie mnie o tym .. Zapraszamy do zgłoszenia NLN.
ankii
2
@ankii Możesz zainstalować z Homebrew (a może MacPorts?). Jeśli nie są one w twoim guście, zbudowanie własnego z linku Github w prawym górnym rogu strony cdecl.org jest trywialne (właśnie zbudowałem go na macOS Mojave). Następnie po prostu skopiuj plik binarny cdecl na PATH. Polecam $ PATH / bin, ponieważ nie ma potrzeby angażowania roota w coś tak prostego jak to.
sigjuice
Och, nie przeczytałem małego akapitu o instalacji w readme. tylko niektóre polecenia i flagi do obsługi zależności. Zainstalowane przy użyciu brew. :)
ankii
1
Kiedy po raz pierwszy to przeczytałem, pomyślałem: „Nigdy nie zejdę na ten poziom”. Następnego dnia go pobrałem.
Ciro Santilli 27 冠状 病 六四 事件 法轮功 法轮功
126

Nie wiem, czy ma oficjalną nazwę, ale nazywam ją Prawicowo-Lewą Rzeczą (TM).

Zacznij od zmiennej, następnie idź w prawo, w lewo i w prawo ... i tak dalej.

int* arr1[8];

arr1 to tablica 8 wskaźników do liczb całkowitych.

int (*arr2)[8];

arr2 jest wskaźnikiem (nawias blokuje prawy-lewy) do tablicy 8 liczb całkowitych.

int *(arr3[8]);

arr3 to tablica 8 wskaźników do liczb całkowitych.

To powinno ci pomóc w przypadku złożonych deklaracji.

GManNickG
źródło
19
Słyszałem, że nazywa się je „regułą spirali”, którą można znaleźć tutaj .
Fouric
6
@InkBlend: Reguła spiralna różni się od reguły prawo-lewa . Pierwsza z nich kończy się niepowodzeniem w przypadkach takich jak int *a[][10]druga.
legends2k
1
@dogeen Myślałem, że ten termin ma coś wspólnego z Bjarne Stroustrup :)
Anirudh Ramanathan
1
Jak powiedzieli InkBlend i legends2k, jest to Reguła Spiralna, która jest bardziej złożona i nie działa we wszystkich przypadkach, więc nie ma powodu, aby z niej korzystać.
kotlomoy
Nie zapomnij ( ) [ ]o lewostronnym połączeniu z prawym i lewym od* &
Mushy
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 
Sunil bn
źródło
Czy trzecia nie powinna być: a to tablica wskaźników do tablicy liczb całkowitych o rozmiarze 8? Mam na myśli, że każda z tablic liczb całkowitych będzie miała rozmiar 8, prawda?
Rushil Paul
2
@Rushil: nie, ostatni indeks dolny ( [5]) reprezentuje wymiar wewnętrzny. Oznacza to, że (*a[8])jest to pierwszy wymiar, a zatem zewnętrzna reprezentacja tablicy. To, na co a wskazuje każdy element, to inna tablica liczb całkowitych o rozmiarze 5.
zeboidlund
Dzięki za trzeci. Szukam sposobu zapisu tablicy wskaźników do tablicy.
Deqing
15

Odpowiedź na dwa ostatnie pytania można również odjąć od złotej reguły w C:

Deklaracja następuje po użyciu.

int (*arr2)[8];

Co się stanie, jeśli będziesz się obawiać arr2? Otrzymasz tablicę 8 liczb całkowitych.

int *(arr3[8]);

Co się stanie, jeśli weźmiesz element arr3? Otrzymasz wskaźnik do liczby całkowitej.

Pomaga to również w przypadku wskaźników do funkcji. Weźmy przykład sigjuice:

float *(*x)(void )

Co się stanie, gdy się wyreżyserujesz x? Otrzymujesz funkcję, którą możesz wywołać bez argumentów. Co się stanie, gdy go nazwiesz? Zwróci wskaźnik do float.

Jednak pierwszeństwo operatorów jest zawsze trudne. Jednak użycie nawiasów może być mylące, ponieważ deklaracja następuje po użyciu. Przynajmniej dla mnie intuicyjnie arr2wygląda jak tablica 8 wskaźników do liczb całkowitych, ale tak naprawdę jest odwrotnie. Wystarczy trochę przyzwyczaić się. Wystarczający powód, aby zawsze dodawać komentarz do tych deklaracji, jeśli mnie o to poprosisz :)

edycja: przykład

Nawiasem mówiąc, natknąłem się na następującą sytuację: funkcję, która ma macierz statyczną i używa arytmetyki wskaźnika, aby sprawdzić, czy wskaźnik wiersza jest poza zakresem. Przykład:

#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;
}

Wynik:

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

Zauważ, że wartość granicy nigdy się nie zmienia, więc kompilator może ją zoptymalizować. Różni się to od tego, czego początkowo możesz chcieć użyć:: const int (*border)[3]deklaruje obramowanie jako wskaźnik do tablicy 3 liczb całkowitych, które nie będą zmieniać wartości tak długo, jak długo istnieje zmienna. Jednak wskaźnik ten może być w dowolnym momencie skierowany na dowolną inną tablicę. Zamiast tego chcemy tego rodzaju zachowanie argumentu (ponieważ ta funkcja nie zmienia żadnej z tych liczb całkowitych). Deklaracja następuje po użyciu.

(ps: możesz poprawić tę próbkę!)

Hraban Luyat
źródło
5
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Byron Formwalt
źródło
3

Jako zasada, prawda operatorów jednoargumentowych (jak [],() itp) preferencji przejąć lewych. Tak, int *(*ptr)()[];będzie to wskaźnik, który wskazuje na funkcję, która zwraca tablicę wskaźników do int (Get Right operatorów tak szybko, jak to możliwe, jak wydostać się z nawiasu)

Luis Colorado
źródło
To prawda, ale jest również nielegalna. Nie możesz mieć funkcji, która zwraca tablicę. Próbowałem i dostałem to: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];pod GCC 8 z$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito
1
Powodem, dla którego kompilator podaje ten błąd, jest to, że interpretuje on funkcję jako zwracającą tablicę, co stanowi poprawna interpretacja reguły pierwszeństwa. Jest to nielegalne jako deklaracja, ale deklaracja prawna int *(*ptr)();pozwala na użycie wyrażenia typu p()[3](lub (*p)()[3]) później.
Luis Colorado,
Ok, jeśli rozumiem, mówisz o utworzeniu funkcji, która zwraca wskaźnik do pierwszego elementu tablicy (a nie samej tablicy), a później używasz tej funkcji, jakby zwracała tablicę? Ciekawy pomysł. Spróbuję tego. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }i nazwij to tak: foo(arr)[4];co powinno zawierać arr[2][4], prawda?
Cacahuete Frito
racja ... ale miałeś rację, a deklaracja była nielegalna. :)
Luis Colorado
2

Myślę, że możemy zastosować prostą zasadę ...

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

ptrjest wskaźnikiem„ idź w prawo .. jego ”)„ teraz idź w lewo to a ”(„ wyjdź idź w prawo ”()„ więc ”do funkcji, która nie przyjmuje argumentów„ idź w lewo ”i zwraca wskaźnik„ idź prawo do tablicy „idź w lewo” liczb całkowitych

simal
źródło
Poprawiłbym to trochę: „ptr to nazwa, która odnosi się do„ idź w prawo ... to ), teraz idź w lewo ... to *„wskaźnik do” idź w prawo ... to ), teraz idź w lewo ... to (wyjdzie, idź w prawo ()tak „do funkcji, która nie wymaga żadnych argumentów” idź w prawo ... []„i zwraca tablicę” idź w prawo ;końca, więc idź w lewo ... *„wskazówek” iść w lewo ... int„całkowite”
Cacahuete Frito
2

Oto jak to interpretuję:

int *something[n];

Uwaga na temat pierwszeństwa: operator indeksu tablicy ( []) ma wyższy priorytet niż operator dereferencji ( *).

Tak więc tutaj zastosujemy []wcześniej *, dzięki czemu oświadczenie będzie równoważne z:

int *(something[i]);

Uwaga na temat tego, w jaki sposób deklaracja ma sens: int numoznacza numtoint , int *ptrlub int (*ptr)oznacza, że ​​(wartość at ptr) jest int, co stanowi ptrwskaźnik do int.

Można to odczytać jako (wartość (wartość w indeksie czegoś)) jest liczbą całkowitą. Tak więc (wartość przy i-tym indeksie czegoś) jest (wskaźnikiem liczb całkowitych), co czyni z tego tablicę wskaźników liczb całkowitych.

W drugim

int (*something)[n];

Aby zrozumieć to oświadczenie, musisz znać ten fakt:

Uwaga na temat wskaźnika reprezentacji tablicy: somethingElse[i]jest równoważna z*(somethingElse + i)

Tak, zastępując somethingElsez (*something), otrzymujemy *(*something + i), który jest liczbą całkowitą, jak na deklaracji. Tak, (*something)dał nam tablicę, która sprawia, że coś odpowiednik (wskaźnik do tablicy) .

nishantbhardwaj2002
źródło
0

Wydaje mi się, że druga deklaracja jest dla wielu myląca. Oto prosty sposób, aby to zrozumieć.

Pozwala mieć tablicę liczb całkowitych, tj int B[8] .

Miejmy także zmienną A, która wskazuje na B. Teraz wartość w A to B, tj (*A) == B . Stąd A wskazuje na tablicę liczb całkowitych. W twoim pytaniu arr jest podobny do A.

Podobnie, w int* (*C) [8], C jest wskaźnikiem do tablicy wskaźników na liczbę całkowitą.

nishantbhardwaj2002
źródło
0
int *arr1[5]

W tej deklaracji arr1jest tablica 5 wskaźników do liczb całkowitych. Powód: nawiasy kwadratowe mają wyższy priorytet niż * (operator wyrzeczenia). W tym typie liczba wierszy jest stała (tutaj 5), ale liczba kolumn jest zmienna.

int (*arr2)[5]

W tej deklaracji arr2jest wskaźnikiem do liczby całkowitej zawierającej 5 elementów. Powód: tutaj nawiasy kwadratowe mają wyższy priorytet niż []. W tym typie liczba wierszy jest zmienna, ale liczba kolumn jest stała (tutaj 5).

Krishna Kanth Yenumula
źródło
-7

We wskaźniku do liczby całkowitej, jeśli wskaźnik jest zwiększany, to przechodzi do następnej liczby całkowitej.

w tablicy wskaźnika, jeśli wskaźnik jest zwiększany, przeskakuje do następnej tablicy

Nikhil
źródło
w tablicy wskaźnika, jeśli wskaźnik jest zwiększany, przeskakuje do następnej tablicy ”, to po prostu źle.
alk