Jak poprawnie skonfigurować, uzyskać dostęp i zwolnić tablicę wielowymiarową w C?

Widziałem dziesiątki pytań o "co jest nie tak z moim kodem" dotyczących wielowymiarowych tablic w C. z jakiegoś powodu ludzie nie mogą się skupić na tym, co się tutaj dzieje, więc postanowiłem odpowiedzieć na to pytanie jako odniesienie do innych:

Jak poprawnie skonfigurować, uzyskać dostęp i zwolnić tablicę wielowymiarową w C?

Jeśli inni mają pomocne porady, prosimy pisać razem!

Author: Mike, 2012-09-17

4 answers

W C od C99, nawet dynamiczne tablice wielowymiarowe mogą być łatwo przydzielane za jednym razem za pomocą malloc i uwalniane za pomocą free:

double (*A)[n] = malloc(sizeof(double[n][n]));

for (size_t i = 0; i < n; ++i)
  for (size_t j = 0; j < n; ++j)
      A[i][j] = someinvolvedfunction(i, j);

free(A);
 26
Author: Jens Gustedt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 15:45:58

Istnieją co najmniej cztery różne sposoby tworzenia lub symulacji wielowymiarowej tablicy w C89.

Jeden to "przydziel każdy wiersz osobno", opisane przez Mike ' a w jego odpowiedzi. Jest to nie wielowymiarowa tablica, tylko imituje jedną (w szczególności naśladuje składnię dostępu do elementu). Może to być przydatne w przypadku, gdy każdy wiersz ma inny rozmiar, więc nie reprezentujesz macierzy, ale raczej coś z "poszarpaną krawędzią".

Jeden to " przeznaczyć a tablica wielowymiarowa". Wygląda tak:

int (*rows)[NUM_ROWS][NUM_COLS] = malloc(sizeof *rows);
...
free(rows);

Wtedy składnia elementu access [i,j] to (*rows)[i][j]. W C89 zarówno NUM_COLS jak i NUM_ROWS muszą być znane w czasie kompilacji. Jest to prawdziwa tablica 2-D i rows jest wskaźnikiem do niej.

Jednym z nich jest "przydzielanie tablicy wierszy". Wygląda to tak:

int (*rows)[NUM_COLS] = malloc(sizeof(*rows) * NUM_ROWS);
...
free(rows);

Wtedy składnia elementu access [i,j] to rows[i][j]. W C89, {[4] } musi być znany w czasie kompilacji. To jest prawdziwa tablica 2-D.

JEDEN to: "przydziel tablicę 1-d i udawaj". Wygląda to tak:

int *matrix = malloc(sizeof(int) * NUM_COLS * NUM_ROWS);
...
free(matrix);

Wtedy składnia elementu access [i,j] to matrix[NUM_COLS * i + j]. To (oczywiście) nie jest prawdziwa tablica 2-D. W praktyce ma taki sam układ jak jeden.

 13
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 16:19:31

Jest to łatwe do zrozumienia ze statycznie rzecz ujmując:

int mtx[3][2] = {{1, 2},
                 {2, 3},
                 {3, 4}};
Nic skomplikowanego. 3 wiersze, 2 kolumny; dane w kolumnie pierwszej: 1, 2, 3; DANE w kolumnie drugiej: 2, 3, 4. Możemy uzyskać dostęp do elementów za pomocą tej samej konstrukcji:
for(i = 0; i<3; i++){
    for(j = 0; j<2; j++)
        printf("%d ", mtx[i][j]);
    printf("\n");
}
//output
//1 2
//2 3
//3 4

Spójrzmy teraz na to w kategoriach wskaźniki :

Nawiasy są bardzo ładną konstrukcją, która pomaga uprościć sprawy, ale nie pomaga, gdy musimy pracować w dynamicznym środowisku, więc musimy myśleć o tym w kategoriach wskazań. Jeśli chcemy zapisać "wiersz" liczb całkowitych, potrzebujemy tablicy:

int row[2] = {1,2};
I wiesz co? Mamy do tego Dostęp jak do wskaźnika.
printf("%d, %d\n",*row,*(row+1));   //prints 1, 2
printf("%d, %d\n",row[0],row[1]);   //prints 1, 2

Teraz, jeśli nie znamy liczby wartości w wierszu, możemy uczynić tę tablicę długością dynamiczną, jeśli mamy wskaźnik do int i dajemy jej trochę pamięci:

int *row = malloc(X * sizeof(int));  //allow for X number of ints
*row = 1;        //row[0] = 1
*(row+1) = 2; //row[1] = 2
…
*(row+(X-1)) = Y; // row[x-1] = Some value y

Więc teraz mamy dynamiczną 1-wymiarową tablicę; jeden wiersz. Ale chcemy wielu rzędów, nie tylko jednego, i nie wiemy ile. To oznacza, że potrzebujemy kolejnego dynamiczna 1-wymiarowa tablica, każdy element tej tablicy będzie wskaźnikiem wskazującym na wiersz.

//we want enough memory to point to X number of rows
//each value stored there is a pointer to an integer
int ** matrix = malloc(X * sizeof(int *));

//conceptually:
(ptr to ptr to int)     (pointer to int)
   **matrix ------------> *row1 --------> [1][2]
                          *row2 --------> [2][3]
                          *row3 --------> [3][4]

Teraz pozostaje tylko napisać kod, który wykona te dynamiczne alokacje:

int i, j, value = 0;

//allocate memory for the pointers to rows
int ** matrix = malloc(Rows * sizeof(int*));

//each row needs a dynamic number of elements
for(i=0; i<Rows; i++){
    // so we need memory for the number of items in each row… 
    // we could call this number of columns as well
    *(matrix + i) = malloc(X * sizeof(int));

     //While we’re in here, if we have the items we can populate the matrix
    for(j=0; j<X; j++)
        *(*(matrix+i)+j) = value; // if you deference (matrix + i) you get the row
                                  // if you add the column and deference again, you
                                  // get the actual item to store (not a pointer!)
}

Jedną z najważniejszych rzeczy do zrobienia teraz jest upewnienie się, że uwolnimy pamięć, kiedy skończymy. Każdy poziom malloc() powinien mieć taką samą liczbę wywołań free(), a wywołania powinny być w kolejności FILO (odwrotność wywołań malloc):

for(i=0; i<Rows; i++) 
    free(*(matrix + i));
free(matrix);

//set to NULL to clean up, matrix points to allocated memory now so let’s not use it!
matrix = NULL; 
 6
Author: Mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 15:42:56

Jeśli chcesz użyć tablicy typedef ' D, jest to jeszcze prostsze.

Powiedz, że masz w swoim kodzie typedef int LabeledAdjMatrix[SIZE][SIZE];

Możesz użyć:

LabeledAdjMatrix *pMatrix = malloc(sizeof(LabeledAdjMatrix));

Wtedy możesz napisać:

for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) (*parr)[i][j] = k++; /* or parr[0][i][j]... */
}

Ponieważ pArr jest wskaźnikiem do macierzy i * ma niższy priorytet niż [];

Dlatego popularnym idiomem mode jest wpisanie wiersza:

typedef int LabeledAdjRow[SIZE];

Wtedy możesz napisać:

LabeledAdjRow *pMatrix = malloc(sizeof(LabeledAdjRow) * SIZE);
for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) parr[i][j] = k++;
}

I możesz memcpy to wszystko bezpośrednio:

LabeledAdjRow *pOther = malloc(sizeof(LabeledAdjRow) * SIZE);
memcpy(pOther, pMatrix, sizeof(LabeledAdjRow) * SIZE);
 1
Author: Serge Ballesta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 06:45:05