2013-03-13 9 views
18

Coś wracam do C, ale mam problem z zapamiętywaniem, jak działa to zarządzanie pamięcią. Chciałbym mieć wskaźnik do tablicy wskaźników do struktur.C: wskaźnik do tablicy wskaźników do struktur (kwestie alokacji/dealokacji)

Say mam:

struct Test { 
    int data; 
}; 

Następnie tablica:

struct Test **array1; 

Czy jest to prawidłowe? Mój problem polega na tym. Tak więc każdy wskaźnik w tablicy wskazuje na coś, co jest przydzielane osobno. Ale myślę, że muszę to zrobić najpierw:

array1 = malloc(MAX * sizeof(struct Test *)); 

Mam problemy ze zrozumieniem powyższego. Czy muszę to zrobić i dlaczego muszę to zrobić? W szczególności, co to znaczy przydzielić pamięć dla wskaźników, jeśli mam zamiar alokować pamięć dla każdej rzeczy, na którą wskazuje wskaźnik?

Powiedzmy, że mam wskaźnik do tablicy wskaźników do struktur. Teraz chcę, żeby wskazywała na tę samą tablicę, którą wcześniej utworzyłem.

struct Test **array2; 

Czy trzeba przeznaczyć pomieszczenie dla wskaźników jak ja powyżej, lub może po prostu zrobić:

array2 = array1 
+0

zobacz http://stackoverflow.com/questions/11421884/memory-allocation-for-array-of-structure-pointers –

+0

Czy chcesz faktycznego zestawu wskaźników do struktur? Jak w zadeklarowanej tablicy, w której alokujesz każdy element za pomocą struktury? – teppic

+0

Cóż, chcę wskaźnik do tablicy, w której mogę wykonać to, co powiedziałeś. – DillPixel

Odpowiedz

48

Przydzielone Array

Z przydzielonej tablicy To dosyć proste do naśladowania.

Zadeklaruj swój zestaw wskaźników. Każdy element tej tablicy punktów do struct Test:

struct Test *array[50]; 

Następnie przydzielić i przypisać wskaźniki do struktur jednak chcesz. Za pomocą pętli byłaby prosta:

array[n] = malloc(sizeof(struct Test)); 

Następnie zadeklarować wskaźnik do tej tablicy:

       // an explicit pointer to an array 
struct Test *(*p)[] = &array; // of pointers to structs 

Pozwala to używać (*p)[n]->data; aby odwołać się do n-tego członka.

Nie martw się, jeśli ten problem jest mylący. To chyba najtrudniejszy aspekt C.


Dynamiczny Linear Array

Jeśli chcesz po prostu przeznaczyć bloku kodowanym (w praktyce tablicę kodowanym, nie wskaźniki do kodowanym) i posiada wskaźnik do bloku, ty można to zrobić łatwiej:

struct Test *p = malloc(100 * sizeof(struct Test)); // allocates 100 linear 
                // structs 

następnie można wskazać tego wskaźnika:

struct Test **pp = &p 

Nie masz już tablic wskaźników do struktur, ale to znacznie upraszcza całą sprawę.


Dynamiczne Array dynamicznie przydzielane elemencie

Najbardziej elastyczne, ale nie często potrzebne. Jest bardzo podobny do pierwszego przykładu, ale wymaga dodatkowej alokacji. Napisałem kompletny program, aby zademonstrować to, co powinno się dobrze skompilować.

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

struct Test { 
    int data; 
}; 

int main(int argc, char **argv) 
{ 
    srand(time(NULL)); 

    // allocate 100 pointers, effectively an array 
    struct Test **t_array = malloc(100 * sizeof(struct Test *)); 

    // allocate 100 structs and have the array point to them 
    for (int i = 0; i < 100; i++) { 
     t_array[i] = malloc(sizeof(struct Test)); 
    } 

    // lets fill each Test.data with a random number! 
    for (int i = 0; i < 100; i++) { 
     t_array[i]->data = rand() % 100; 
    } 

    // now define a pointer to the array 
    struct Test ***p = &t_array; 
    printf("p points to an array of pointers.\n" 
     "The third element of the array points to a structure,\n" 
     "and the data member of that structure is: %d\n", (*p)[2]->data); 

    return 0; 
} 

wyjściowa:

> p points to an array of pointers. 
> The third element of the array points to a structure, 
> and the data member of that structure is: 49 

lub cały zestaw:

for (int i = 0; i < 100; i++) { 
    if (i % 10 == 0) 
     printf("\n"); 
    printf("%3d ", (*p)[i]->data); 
} 

35 66 40 24 32 27 39 64 65 26 
32 30 72 84 85 95 14 25 11 40 
30 16 47 21 80 57 25 34 47 19 
56 82 38 96 6 22 76 97 87 93 
75 19 24 47 55 9 43 69 86 6 
61 17 23 8 38 55 65 16 90 12 
87 46 46 25 42 4 48 70 53 35 
64 29 6 40 76 13 1 71 82 88 
78 44 57 53 4 47 8 70 63 98 
34 51 44 33 28 39 37 76 9 91 

Dynamiczny wskaźnik tablicy Single-Dynamic Przydzielone elemencie

Ten ostatni przykład jest raczej specyficzny. Jest to dynamiczna tablica wskaźników, jak widzieliśmy w poprzednich przykładach, ale w przeciwieństwie do nich elementy są alokowane w pojedynczym przydziale pojedynczej. Ma to swoje zastosowania, najbardziej godne uwagi przy sortowaniu danych w różnych konfiguracjach, pozostawiając niezmienioną oryginalną alokację.

Zaczynamy przeznaczając pojedynczy blok elementów jak my w najbardziej podstawowym alokacji pojedynczego bloku:

struct Test *arr = malloc(N*sizeof(*arr)); 

Teraz możemy przeznaczyć oddzielny blok wskaźników:

struct Test **ptrs = malloc(N*sizeof(*ptrs)); 

Następnie zapełniamy każdy slot na naszej liście wskaźnikowej adresem jednej z naszych oryginalnych tablic. Ponieważ wskaźnik arytmetyka pozwala nam przenieść się z elementem adresu elementu, to jest prosta:

for (int i=0;i<N;++i) 
    ptrs[i] = arr+i; 

W tym momencie po odnoszą się do tego samego elementu pola

arr[1].data = 1; 
ptrs[1]->data = 1; 

i po przeglądu wyżej , Mam nadzieję, że jest jasne, , dlaczego.

Kiedy skończymy z tablicy wskaźnik i oryginalnej tablicy bloku są uwolnione jako:

free(ptrs); 
free(arr); 

Uwaga: Nie uwolnić każdy element w tablicy ptrs[] indywidualnie. To nie jest sposób ich alokacji. Zostały one przydzielone jako pojedynczy blok (wskazany przez arr) i tak powinny zostać uwolnione.

Dlaczego więc ktoś chciałby to zrobić? Kilka powodów.

Po pierwsze radykalnie zmniejsza liczbę wywołań przydziału pamięci. Zamiast tego N+1 (jeden dla tablicy wskaźnikowej, N dla poszczególnych struktur) masz teraz tylko dwa: jeden dla bloku tablicowego i jeden dla tablicy wskaźnikowej. Przydzielanie pamięci jest jedną z najdroższych operacji, które program może zażądać, a gdy jest to możliwe, pożądane jest ich minimalizowanie (uwaga: plik IO jest inny, fyi).

Kolejny powód: wiele reprezentacji tej samej podstawowej macierzy danych. Załóżmy, że chcesz posortować dane zarówno rosnąco, jak i malejąco i posortować obie reprezentacje w tym samym czasie: w tym samym czasie:. Można powielić macierz danych, ale wymagałoby to dużej ilości kopiowania i spożywania znacznej ilości pamięci. Zamiast tego po prostu przydziel dodatkową tablicę wskaźnika i wypełnij ją adresami z tablicy podstawowej, a następnie posortuj tablicę wskaźnika. Ma to szczególne znaczenie, gdy sortowane dane są duże (być może kilobajtów lub nawet większe, na element). Oryginalne elementy pozostają w swoich pierwotnych lokalizacjach w macierzy podstawowej, ale teraz masz bardzo wydajny mechanizm, w którym możesz je sortować bez konieczności faktycznego przeniesienia ich. Sortujesz tablicę wskaźników do pozycji; przedmioty w ogóle się nie poruszają.

Zdaję sobie sprawę, że jest to bardzo trudne do przyjęcia, ale użycie wskaźnika ma kluczowe znaczenie dla zrozumienia wielu potężnych rzeczy, które możesz zrobić w języku C, więc uderz w książki i odśwież swoją pamięć. Wróci.

+0

Powiedzmy, że mam inną strukturę Test2, która przechowuje ten wskaźnik do tablicy. Jak przydzieliłbym to na stercie? struct Test2 {struct Test * array [50]; }; struct Test2 * container = malloc (sizeof (Test2)) Czy to wystarczy? – DillPixel

+0

@DillPixel: To deklaruje samą tablicę wskaźników w 2. strukturze. Jeśli chcesz, aby struktura wskazywała tablicę, musisz tylko zdefiniować wskaźnik. (To zaczyna mnie boleć) – teppic

+1

Czy istnieje terminologia dla każdego wymienionego tutaj typu alokacji dynamicznej? Chciałbym móc wyszukiwać powiązane rzeczy w wyszukiwarce Google. Wcześniej rozumiałem "Dynamiczny macierz liniowy" i "Dynamiczną tablicę dynamicznie przydzielanych struktur", ale nie wiem, jak wyrazić je w wyszukiwarce Google inaczej niż przy dynamicznym przydzielaniu macierzy. – raymai97

2

I sugerują, że to zbudować warstwę naraz używając typdefs do tworzenia warstw typów. W ten sposób różne potrzebne typy będą znacznie wyraźniejsze.

Na przykład:

typedef struct Test { 
    int data; 
} TestType; 

typedef TestType * PTestType; 

Spowoduje to utworzenie dwóch nowych typów, jeden dla struktury i jeden dla wskaźnika do struktury.

Więc następnym jeśli chcesz tablicą z kodowanym wtedy użyć:

TestType array[20]; // creates an array of 20 of the structs 

Jeśli chcesz tablicę wskaźników do kodowanym wtedy użyłby:

PTestType array2[20]; // creates an array of 20 of pointers to the struct 

Następnie, jeśli chcesz przydzielić structs do tablicy, możesz zrobić coś takiego:

PTestType array2[20]; // creates an array of 20 of pointers to the struct 
// allocate memory for the structs and put their addresses into the array of pointers. 
for (int i = 0; i < 20; i++) { 
    array2 [i] = malloc (sizeof(TestType)); 
} 

C nie zezwala na Ciebie u, aby przypisać jedną tablicę do drugiej. Zamiast tego należy użyć pętli, aby przypisać każdy element jednej tablicy do elementu drugiej.

EDIT: Innym interesującym podejściem

Inne podejście byłoby bardziej obiektowe podejście, w którym upakować kilka rzeczy. Na przykład przy użyciu tych samych typów warstw tworzymy dwa typy:

typedef struct _TestData { 
    struct { 
     int myData; // one or more data elements for each element of the pBlob array 
    } *pBlob; 
    int nStructs;   // count of number of elements in the pBlob array 
} TestData; 

typedef TestData *PTestData; 

Następnie mamy funkcję pomocnika, który używamy, aby utworzyć obiekt o nazwie wystarczy odpowiednio CreateTestData (int nArrayCount).

PTestData CreateTestData (int nCount) 
{ 
    PTestData ret; 

    // allocate the memory for the object. we allocate in a single piece of memory 
    // the management area as well as the array itself. We get the sizeof() the 
    // struct that is referenced through the pBlob member of TestData and multiply 
    // the size of the struct by the number of array elements we want to have. 
    ret = malloc (sizeof(TestData) + sizeof(*(ret->pBlob)) * nCount); 
    if (ret) { // make sure the malloc() worked. 
      // the actual array will begin after the end of the TestData struct 
     ret->pBlob = (void *)(ret + 1); // set the beginning of the array 
     ret->nStructs = nCount;   // set the number of array elements 
    } 

    return ret; 
} 

Teraz możemy użyć naszego nowego obiektu, jak w poniższym kodzie źródłowym. Powinien sprawdzić, czy wskaźnik zwrócony z CreateTestData() jest poprawny, ale tak naprawdę pokazuje tylko, co można zrobić.

PTestData go = CreateTestData (20); 
{ 
    int i = 0; 
    for (i = 0; i < go->nStructs; i++) { 
     go->pBlob[i].myData = i; 
    } 
} 

W prawdziwie dynamicznym środowisku można też chcą mieć ReallocTestData(PTestData p) funkcję, która będzie przesunięciu się TestData obiektu w celu zmiany rozmiaru tablicy znajdującej się w obiekcie.

Przy takim podejściu, po zakończeniu pracy z określonym obiektem TestData, można po prostu zwolnić obiekt jak w free (go), a obiekt i jego tablica są jednocześnie zwalniane.

Edit: Rozszerzanie Dalsze

Z obudowanymi typu możemy teraz zrobić kilka innych ciekawych rzeczy. Na przykład możemy mieć funkcję kopiowania, PTestType CreateCopyTestData (PTestType pSrc), która utworzy nową instancję, a następnie skopiuje argument do nowego obiektu. W poniższym przykładzie ponownie wykorzystujemy funkcję PTestType CreateTestData (int nCount), która stworzy instancję naszego typu, używając rozmiaru kopiowanego obiektu. Po utworzeniu nowego obiektu tworzymy kopię danych z obiektu źródłowego. Ostatnim krokiem jest poprawienie wskaźnika, który w źródłowym obiekcie wskazuje na jego obszar danych, tak aby wskaźnik w nowym obiekcie wskazywał teraz na obszar danych, a nie na obszar danych starego obiektu.

PTestType CreateCopyTestData (PTestType pSrc) 
{ 
    PTestType pReturn = 0; 

    if (pSrc) { 
     pReturn = CreateTestData (pSrc->nStructs); 

     if (pReturn) { 
      memcpy (pReturn, pSrc, sizeof(pTestType) + pSrc->nStructs * sizeof(*(pSrc->pBlob))); 
      pReturn->pBlob = (void *)(pReturn + 1); // set the beginning of the array 
     } 
    } 

    return pReturn; 
} 
3

Może lepiej jest zadeklarować rzeczywistą tablicę, jak sugerowali inni, ale twoje pytanie wydaje się bardziej dotyczyć zarządzania pamięcią, więc omówię to. Jest to wskaźnik adresu adresu struct Test. (Nie jest to wskaźnik do samej struktury, jest to wskaźnik do miejsca w pamięci, które przechowuje adres struktury). Deklaracja przydziela pamięć dla wskaźnika, ale nie dla elementów, które wskazuje. Ponieważ do tablicy można uzyskać dostęp za pomocą wskaźników, można pracować z *array1 jako wskaźnikiem do tablicy, której elementy są typu struct Test. Ale nie ma jeszcze rzeczywistej tablicy do wskazania.

array1 = malloc(MAX * sizeof(struct Test *)); 

ten przydziela pamięć do przechowywania MAX wskaźniki do elementów typu struct Test. Ponownie, nie przydziela pamięci dla samych struktur; tylko dla listy wskaźników. Ale teraz możesz traktować array jako wskaźnik do przydzielonej tablicy wskaźników. Aby uzyskać numer array1, należy użyć rzeczywistych struktur.Można to zrobić w prosty sposób deklarowania każdej struct z

struct Test testStruct0; // Declare a struct. 
struct Test testStruct1; 
array1[0] = &testStruct0; // Point to the struct. 
array1[1] = &testStruct1; 

można również przydzielić przypisać struktury na stercie:

for (int i=0; i<MAX; ++i) { 
    array1[i] = malloc(sizeof(struct Test)); 
} 

Po przydzielona pamięć, można utworzyć nową zmienną, która wskazuje ta sama lista elemencie:

struct Test **array2 = array1; 

nie trzeba przeznaczyć żadnej dodatkowej pamięci, ponieważ array2 wskazuje na samej pamięci masz przydzielonej array1.


Czasami chcesz mieć wskaźnik do listy wskaźników, ale jeśli robisz coś ochotę, może być w stanie wykorzystać

struct Test *array1 = malloc(MAX * sizeof(struct Test)); // Pointer to MAX structs 

Ten deklaruje wskaźnik array1, przydzielono wystarczającą ilość pamięci dla struktur MAX i punkty array1 do tej pamięci. Teraz można przejść do konstrukcjom tak:

struct Test testStruct0 = array1[0];  // Copies the 0th struct. 
struct Test testStruct0a= *array1;  // Copies the 0th struct, as above. 
struct Test *ptrStruct0 = array1;  // Points to the 0th struct. 

struct Test testStruct1 = array1[1];  // Copies the 1st struct. 
struct Test testStruct1a= *(array1 + 1); // Copies the 1st struct, as above. 
struct Test *ptrStruct1 = array1 + 1; // Points to the 1st struct. 
struct Test *ptrStruct1 = &array1[1]; // Points to the 1st struct, as above. 

Więc co za różnica? Kilka rzeczy. Oczywiście pierwsza metoda wymaga przydzielenia pamięci dla wskaźników, a następnie przydzielenia dodatkowej przestrzeni dla samych struktur; drugi pozwala uciec z jednym połączeniem malloc(). Co kupuje ci dodatkowa praca?

Ponieważ pierwsza metoda daje rzeczywisty zestaw wskaźników do struktur Test, każdy wskaźnik może wskazywać dowolną strukturę w dowolnym miejscu w pamięci; nie muszą być przyległe. Co więcej, możesz przydzielić i zwolnić pamięć dla każdej faktycznej struktury Test, jeśli jest to konieczne, i możesz ponownie przypisać wskaźniki. Tak więc, na przykład, można zamienić dwie struktury, po prostu wymieniając swoje wskaźniki:

struct Test *tmp = array1[2]; // Save the pointer to one struct. 
array1[2] = array1[5];   // Aim the pointer at a different struct. 
array1[5] = tmp;    // Aim the other pointer at the original struct. 

Z drugiej strony, druga metoda przydziela jeden ciągły blok pamięci dla wszystkich Test elemencie i partycji go do MAX przedmiotów . A każdy element tablicy znajduje się w ustalonej pozycji; jedynym sposobem na zamianę dwóch struktur jest ich skopiowanie.

Wskaźniki są jednym z najbardziej użytecznych konstruktów w C, ale mogą również należeć do najtrudniejszych do zrozumienia. Jeśli planujesz kontynuować korzystanie z C, prawdopodobnie warto zainwestować w grę ze wskaźnikami, tablicami i debuggerem, dopóki nie poczujesz się z nimi komfortowo.

Powodzenia!

2

Struktury nie różnią się zbytnio od innych obiektów. Zacznijmy znaków:

char *p; 
p = malloc (CNT * sizeof *p); 

* p jest postacią, więc sizeof *p jest sizeof (char) == 1; przydzieliliśmy znaki CNT. Dalej:

char **pp; 
pp = malloc (CNT * sizeof *pp); 

* p jest wskaźnikiem do znaku, więc sizeof *pp jest sizeof (char *). Przydzieliliśmy wskaźniki CNT.Dalej:

struct something *p; 
p = malloc (CNT * sizeof *p); 

* p jest struct coś, więc sizeof *p jest sizeof (struct coś). Przeznaczyliśmy strukturę CNT. Dalej:

struct something **pp; 
pp = malloc (CNT * sizeof *pp); 

* pp to wskaźnik do struct, więc sizeof *pp jest sizeof (struct coś *). Przydzieliliśmy wskaźniki CNT.

+0

nie jest sizeof wskaźnik na char 8 bajtów? – Yar

+0

@Yar To mogłoby być. Może to być 4, a może nawet 2 ... Nie ma to znaczenia. Jest to również powód, dla którego istnieje "sizeof". – wildplasser