W C wiem, że mogę dynamicznie przydzielić tablicę dwuwymiarową na stercie za pomocą następującego kodu:
int** someNumbers = malloc(arrayRows*sizeof(int*));
for (i = 0; i < arrayRows; i++) {
someNumbers[i] = malloc(arrayColumns*sizeof(int));
}
Najwyraźniej tworzy to jednowymiarową tablicę wskaźników do szeregu oddzielnych jednowymiarowych tablic liczb całkowitych, a „System” może zrozumieć, co mam na myśli, gdy pytam:
someNumbers[4][2];
Ale kiedy statycznie deklaruję tablicę 2D, jak w poniższym wierszu ...:
int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];
... czy na stosie tworzona jest podobna struktura, czy też ma zupełnie inną formę? (tj. czy jest to tablica wskaźników 1D? Jeśli nie, co to jest i jak się do tego odnoszą?)
Poza tym, kiedy powiedziałem „System”, co właściwie jest odpowiedzialne za to? Jądro? A może kompilator C rozwiązuje problem podczas kompilacji?
c
arrays
memory
data-structures
stack-memory
Chris Cooper
źródło
źródło
malloc()
: zapętlanie i wywoływanie nie powoduje utworzenia macierzy N-wymiarowej. . Wynikiem tego są tablice wskaźników [do tablic wskaźników [...]], aby całkowicie oddzielić tablice jednowymiarowe . Zobacz Prawidłowe przydzielanie tablic wielowymiarowych, aby zobaczyć, jak przydzielić PRAWDZIWĄ tablicę N-wymiarową.Odpowiedzi:
Statyczna dwuwymiarowa tablica wygląda jak tablica tablic - jest po prostu ułożona w sposób ciągły w pamięci. Tablice to nie to samo, co wskaźniki, ale ponieważ często można ich używać zamiennie, czasami może być mylące. Kompilator śledzi jednak poprawnie, co sprawia, że wszystko ładnie się układa. Musisz uważać na statyczne tablice 2D, jak wspomniałeś, ponieważ jeśli spróbujesz przekazać jedną do funkcji przyjmującej
int **
parametr, coś złego się wydarzy. Oto szybki przykład:W pamięci wygląda następująco:
dokładnie taki sam jak:
Ale jeśli spróbujesz przejść
array1
do tej funkcji:dostaniesz ostrzeżenie (a aplikacja nie uzyska dostępu do tablicy poprawnie):
Ponieważ tablica 2D nie jest taka sama jak
int **
. Można powiedzieć, że automatyczne rozkładanie tablicy na wskaźnik przechodzi tylko „na jeden poziom”. Musisz zadeklarować funkcję jako:lub
Aby wszystko było szczęśliwe.
Ta sama koncepcja rozciąga się na tablice n- wymiarowe. Jednak korzystanie z tego rodzaju śmiesznego biznesu w twojej aplikacji sprawia, że trudniej go zrozumieć. Bądź więc ostrożny.
źródło
sizeof(int[100]) != sizeof(int *)
(chyba że znajdziesz platformę z100 * sizeof(int)
bajtami /int
, ale to inna sprawa.Odpowiedź opiera się na założeniu, że C tak naprawdę nie ma tablic 2D - ma tablice tablic. Kiedy to zadeklarujesz:
Pytasz o
someNumbers
bycie tablicą 4 elementów, gdzie każdy element tej tablicy jest typuint [2]
(który sam jest tablicą 2int
s).Inną częścią układanki jest to, że tablice są zawsze ułożone w sposób ciągły w pamięci. Jeśli poprosisz o:
wtedy to zawsze będzie wyglądać tak:
(4
sometype_t
obiekty ułożone obok siebie, bez odstępów między nimi). W twojejsomeNumbers
tablicy tablic będzie to wyglądać następująco:Każdy
int [2]
element jest tablicą, która wygląda następująco:Więc ogólnie otrzymujesz:
źródło
int
w tablicy tablic (np. poprzez ocenęa[0]
lub&a[0][0]
), to tak, możesz to zrównoważyć, aby uzyskać dostęp sekwencyjny do każdegoint
).w pamięci jest równy:
źródło
W odpowiedzi również na: Oba, choć kompilator wykonuje większość ciężkich zadań.
W przypadku tablic przydzielanych statycznie kompilatorem będzie „System”. Zarezerwuje pamięć tak, jak dla każdej zmiennej stosu.
W przypadku tablicy malloc'd „System” będzie implementatorem malloc (zwykle jądro). Jedyny kompilator, który przydzieli, to wskaźnik podstawowy.
Kompilator zawsze będzie obsługiwał typ taki, jaki jest zadeklarowany, z wyjątkiem przykładu podanego przez Carl, w którym może on określić zastosowanie wymienne. Dlatego jeśli przekazujesz [] [] do funkcji, musisz założyć, że jest to mieszkanie przypisane statycznie, gdzie ** przyjmuje się, że jest wskaźnikiem do wskaźnika.
źródło
malloc
implementacji nie są określone przez standard i pozostawione implementacji, odpowiednio. środowisko. W środowiskach wolnostojących jest on opcjonalny, podobnie jak wszystkie części standardowej biblioteki wymagające funkcji łączenia (właśnie to powodują wymagania, a nie dosłownie to, co określają standardowe). W niektórych współczesnych środowiskach hostowanych, faktycznie opiera się on na funkcjach jądra, albo kompletnych rzeczach, albo (np. Linux), jak pisałeś, używając zarówno stdlib, jak i jąder-prymitywów. W przypadku systemów jednoprocesowych z pamięcią inną niż wirtualna może to być tylko stdlib.Załóżmy, że mamy
a1
ia2
zdefiniowana jak poniżej i inicjalizowana (C99)a1
jest jednorodną tablicą 2D z prostym ciągłym układem w pamięci, a wyrażenie(int*)a1
jest oceniane na wskaźnik do pierwszego elementu:a2
jest inicjowany z heterogenicznej tablicy 2D i jest wskaźnikiem do wartości typuint*
, tzn. wyrażenie dereferencyjne*a2
przekształca się w wartość typuint*
, układ pamięci nie musi być ciągły:Pomimo całkowicie odmiennego układu pamięci i semantyki dostępu, gramatyka języka C dla wyrażeń dostępu do tablicy wygląda dokładnie tak samo dla homogenicznej i heterogenicznej tablicy 2D:
a1[1][0]
pobierze wartość144
za1
tablicya2[1][0]
pobierze wartość244
za2
tablicyKompilator wie, że wyrażenie dostępu dla typu
a1
działaint[2][2]
, gdy wyrażenie dostępu dla typua2
działaint**
. Wygenerowany kod zestawu będzie zgodny z semantyką dostępu jednorodnego lub heterogenicznego.Kod zwykle ulega awarii w czasie wykonywania, gdy tablica typu
int[N][M]
jest rzutowana na typint**
, a następnie dostępna jako typ , na przykład:źródło
Aby uzyskać dostęp do konkretnej tablicy 2D, rozważ mapę pamięci dla deklaracji tablicy, jak pokazano w poniższym kodzie:
Aby uzyskać dostęp do każdego elementu, wystarczy przekazać tablicę, którą jesteś zainteresowany, jako parametry funkcji. Następnie użyj przesunięcia dla kolumny, aby uzyskać dostęp do każdego elementu osobno.
źródło