C: Zeiger auf Array von Zeigern auf Strukturen (Zuweisungs-/Zuweisungsaufhebungsprobleme)

Lesezeit: 14 Minuten

Benutzeravatar von DillPixel
DillPixel

Ich bin aus irgendeinem Grund wieder in C eingestiegen, aber ich habe Probleme, mich an vieles zu erinnern, wie diese Speicherverwaltung funktioniert. Ich möchte einen Zeiger auf ein Array von Zeigern auf Strukturen haben.

Sag ich habe:

struct Test {
   int data;
};

Dann das Array:

struct Test **array1;

Ist das richtig? Mein Problem arbeitet mit diesem Ding. Jeder Zeiger im Array zeigt also auf etwas, das separat zugewiesen wird. Aber ich denke, ich muss das zuerst tun:

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

Ich habe Schwierigkeiten, das oben Gesagte zu verstehen. Muss ich das tun und warum muss ich das tun? Was bedeutet es insbesondere, Speicher für Zeiger zuzuweisen, wenn ich Speicher für jedes Ding zuordnen möchte, auf das der Zeiger zeigt?

Angenommen, ich habe einen Zeiger auf ein Array von Zeigern auf Strukturen. Ich möchte jetzt, dass es auf dasselbe Array zeigt, das ich zuvor erstellt habe.

struct Test **array2;

Muss ich wie oben Platz für Zeiger zuweisen, oder kann ich einfach Folgendes tun:

array2 = array1

  • schau mal unter stackoverflow.com/questions/11421884/…

    – Richard Kammern

    13. März 2013 um 22:31 Uhr

  • Möchten Sie ein tatsächliches Array von Zeigern auf die Strukturen? Wie in einem deklarierten Array, in dem Sie jedem Element eine Struktur zuweisen?

    – teppisch

    13. März 2013 um 22:35 Uhr

  • Nun, ich möchte einen Zeiger auf ein Array, in dem ich ausführen kann, was Sie gesagt haben.

    – DillPixel

    13. März 2013 um 22:36 Uhr


  • Ich weiss. Aber willst du ein real Array, um diese zu halten? Anstatt nur ein Zeiger auf einen Speicherblock, meine ich.

    – teppisch

    13. März 2013 um 22:37 Uhr

  • Es ist einfach einfacher mit einem richtigen Array – ich kann ein Beispiel posten, wenn Sie möchten.

    – teppisch

    13. März 2013 um 22:39 Uhr

Benutzeravatar von teppic
teppisch

Zugewiesenes Array

Mit einem zugewiesenen Array ist es einfach genug zu folgen.

Deklarieren Sie Ihr Array von Zeigern. Jedes Element in diesem Array zeigt auf a struct Test:

struct Test *array[50];

Dann allokieren und ordnen Sie die Pointer den Strukturen beliebig zu. Die Verwendung einer Schleife wäre einfach:

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

Deklarieren Sie dann einen Zeiger auf dieses Array:

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

Dies ermöglicht Ihnen die Verwendung (*p)[n]->data; um auf das n-te Mitglied zu verweisen.

Machen Sie sich keine Sorgen, wenn dieses Zeug verwirrend ist. Es ist wahrscheinlich der schwierigste Aspekt von C.


Dynamisches lineares Array

Wenn Sie nur einen Block von Strukturen zuweisen möchten (effektiv ein Array von Strukturen, nicht Zeiger auf Strukturen) und einen Zeiger auf den Block haben, können Sie es einfacher machen:

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

Sie können dann auf diesen Zeiger zeigen:

struct Test **pp = &p

Sie haben kein Array von Zeigern mehr auf Strukturen, aber es vereinfacht die ganze Sache erheblich.


Dynamisches Array von dynamisch zugewiesenen Strukturen

Am flexibelsten, aber nicht oft benötigt. Es ist dem ersten Beispiel sehr ähnlich, erfordert jedoch eine zusätzliche Zuordnung. Ich habe ein vollständiges Programm geschrieben, um dies zu demonstrieren, das gut kompilieren sollte.

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

Ausgabe:

> 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

Oder das ganze Set:

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 

Dynamisches Zeigerarray von einzeln dynamisch zugewiesenen Strukturen

Dieses letzte Beispiel ist ziemlich spezifisch. Es ist ein dynamisches Array von Zeigern, wie wir in den vorherigen Beispielen gesehen haben, aber im Gegensatz zu diesen werden die Elemente alle in a zugewiesen Single Zuweisung. Dies hat seinen Nutzen, vor allem zum Sortieren von Daten in verschiedenen Konfigurationen, während die ursprüngliche Zuordnung ungestört bleibt.

Wir beginnen mit der Zuweisung eines einzelnen Blocks von Elementen, wie wir es bei der einfachsten Einzelblockzuweisung tun:

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

Nun weisen wir a zu getrennt Block von Zeigern:

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

Wir füllen dann jeden Slot in unserer Zeigerliste mit der Adresse eines unserer ursprünglichen Arrays. Da die Zeigerarithmetik es uns ermöglicht, von Element zu Elementadresse zu wechseln, ist dies einfach:

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

An dieser Stelle beziehen sich die folgenden beiden auf dasselbe Elementfeld

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

Und nachdem ich das Obige überprüft habe, hoffe ich, dass es klar ist warum.

Wenn wir mit dem Pointer-Array und dem ursprünglichen Block-Array fertig sind, werden sie wie folgt freigegeben:

free(ptrs);
free(arr);

Hinweis: Wir geben NICHT jeden Artikel frei ptrs[] einzeln anordnen. So wurden sie nicht zugeteilt. Sie wurden als ein einziger Block zugewiesen (zeigt auf by arr), und so sollten sie befreit werden.

Warum sollte jemand das tun wollen? Mehrere Gründe.

Erstens reduziert es die Anzahl der Speicherzuordnungsaufrufe radikal. Eher, als N+1 (Eins für das Pointer-Array, N für einzelne Strukturen) Sie haben jetzt nur noch zwei: eine für den Array-Block und eine für das Pointer-Array. Speicherzuweisungen sind eine der teuersten Operationen, die ein Programm anfordern kann, und wenn möglich, ist es wünschenswert, sie zu minimieren (Anmerkung: Datei-IO ist eine andere, zur Info).

Ein weiterer Grund: Mehrere Darstellungen des gleichen Basisarrays von Daten. Angenommen, Sie möchten die Daten sowohl aufsteigend als auch absteigend sortieren und haben beide sortierten Darstellungen zur Verfügung zur selben Zeit. Sie könnten das Datenarray duplizieren, aber das würde viel Kopieren erfordern und einen erheblichen Speicherverbrauch verursachen. Weisen Sie stattdessen einfach ein zusätzliches Zeigerarray zu und füllen Sie es mit Adressen aus dem Basisarray und sortieren Sie dann dieses Zeigerarray. Dies hat besonders erhebliche Vorteile, wenn die zu sortierenden Daten groß sind (vielleicht Kilobyte oder sogar noch größer pro Element). Die ursprünglichen Elemente verbleiben an ihren ursprünglichen Speicherorten im Basisarray, aber jetzt haben Sie einen sehr effizienten Mechanismus, mit dem Sie sie sortieren können ohne eigentlich zu müssen Bewegung Sie. Sie sortieren das Array von Zeigern auf Elemente; Die Gegenstände werden überhaupt nicht bewegt.

Mir ist klar, dass dies eine Menge zu verinnerlichen ist, aber die Verwendung von Zeigern ist entscheidend, um die vielen mächtigen Dinge zu verstehen, die Sie mit der Sprache C tun können, also schlagen Sie die Bücher ein und frischen Sie Ihr Gedächtnis auf. Es wird zurückkommen.

  • Angenommen, ich habe eine andere Struktur Test2, die diesen Zeiger auf das Array enthält. Wie würde ich das auf dem Heap zuweisen? struct Test2 { struct Test *array[50]; }; struct Test2 *container = malloc(sizeof(Test2)) Reicht das?

    – DillPixel

    13. März 2013 um 22:57 Uhr


  • @DillPixel: Das deklariert das Array von Zeigern selbst in der 2. Struktur. Wenn Sie nur möchten, dass eine Struktur auf das Array zeigt, müssen Sie nur einen Zeiger definieren. (Das fängt an, meinen Kopf zu verletzen)

    – teppisch

    13. März 2013 um 23:04 Uhr

  • Gibt es eine Terminologie für jede hier erwähnte Art der dynamischen Zuordnung? Ich möchte in der Lage sein, verwandte Dinge zu googlen. Zuvor habe ich “Dynamic Linear Array” und “Dynamic Array of Dynamically Allocated Structs” irgendwie verstanden, weiß aber nicht, wie ich sie in einem anderen Google-Suchbegriff als Dynamic Array Allocation ausdrücken soll.

    – raymai97

    3. Juni 2017 um 12:47 Uhr

  • Erstaunliche Antwort. Dies sollte ein Artikel/Blog/Medium-Beitrag sein.

    – Kingkong Jnr

    8. Juni 2019 um 16:35 Uhr

Es ist vielleicht besser, ein tatsächliches Array zu deklarieren, wie andere vorgeschlagen haben, aber Ihre Frage scheint sich mehr auf die Speicherverwaltung zu beziehen, also werde ich das besprechen.

struct Test **array1;

Dies ist ein Zeiger auf die Adresse von a struct Test. (Kein Zeiger auf die Struktur selbst; es ist ein Zeiger auf einen Speicherort, der die enthält die Anschrift der Struktur.) Die Deklaration reserviert Speicher für den Zeiger, aber nicht für die Elemente, auf die er zeigt. Da auf ein Array über Zeiger zugegriffen werden kann, können Sie damit arbeiten *array1 als Zeiger auf ein Array, dessen Elemente vom Typ sind struct Test. Aber es gibt noch kein tatsächliches Array, auf das es zeigen könnte.

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

Dadurch wird Speicher zum Halten zugewiesen MAX Zeiger auf Elemente des Typs struct Test. Wieder tut es nicht Weisen Sie Speicher für die Strukturen selbst zu; nur für eine Liste von Zeigern. Aber jetzt können Sie behandeln array als Zeiger auf ein zugewiesenes Array von Zeigern.

Um zu verwenden array1, müssen Sie die eigentlichen Strukturen erstellen. Sie können dies tun, indem Sie einfach jede Struktur mit deklarieren

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

Sie können die Strukturen auch auf dem Heap zuweisen:

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

Sobald Sie Speicher zugewiesen haben, können Sie eine neue Variable erstellen, die auf dieselbe Liste von Strukturen verweist:

struct Test **array2 = array1;

Sie müssen keinen zusätzlichen Speicher zuweisen, weil array2 zeigt auf denselben Speicher, dem Sie zugewiesen haben array1.


Manchmal …… du wollen um einen Zeiger auf eine Liste von Zeigern zu haben, aber es sei denn, Sie machen etwas Besonderes, können Sie verwenden

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

Dies deklariert den Zeiger array1zugewiesen genug Speicher für MAX Strukturen und Punkte array1 zu dieser Erinnerung. Jetzt können Sie wie folgt auf die Strukturen zugreifen:

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.

Was ist also der Unterschied? Ein paar Dinge. Natürlich erfordert die erste Methode, dass Sie Speicher für die Zeiger zuweisen und dann zusätzlichen Platz für die Strukturen selbst zuweisen; die zweite lässt Sie mit einem davonkommen malloc() Anruf. Was bringt dir die Mehrarbeit?

Da die erste Methode Ihnen ein tatsächliches Array von Zeigern gibt Test Strukturen kann jeder Zeiger auf beliebige zeigen Test struct, irgendwo im Speicher; sie müssen nicht zusammenhängend sein. Darüber hinaus können Sie den Speicher für jeden Vorgang zuweisen und freigeben Test struct nach Bedarf, und Sie können die Zeiger neu zuweisen. So können Sie beispielsweise zwei Strukturen vertauschen, indem Sie einfach ihre Zeiger austauschen:

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.

Andererseits weist die zweite Methode einen einzigen zusammenhängenden Speicherblock für alle zu Test strukturiert und partitioniert es in MAX Artikel. Und jedes Element im Array befindet sich an einer festen Position; Die einzige Möglichkeit, zwei Strukturen auszutauschen, besteht darin, sie zu kopieren.

Zeiger sind eines der nützlichsten Konstrukte in C, aber sie können auch zu den am schwierigsten zu verstehenden gehören. Wenn Sie weiterhin C verwenden möchten, ist es wahrscheinlich eine lohnende Investition, einige Zeit damit zu verbringen, mit Zeigern, Arrays und einem Debugger herumzuspielen, bis Sie damit vertraut sind.

Viel Glück!

Benutzeravatar von Richard Chambers
Richard Kammern

Ich schlage vor, dass Sie dies Schicht für Schicht aufbauen, indem Sie typdefs verwenden, um Ebenen von Typen zu erstellen. Dadurch werden die verschiedenen benötigten Typen viel klarer.

Zum Beispiel:

typedef struct Test {
   int data;
} TestType;

typedef  TestType * PTestType;

Dadurch werden zwei neue Typen erstellt, einer für die Struktur und einer für einen Zeiger auf die Struktur.

Wenn Sie also als nächstes ein Array der Strukturen wünschen, würden Sie Folgendes verwenden:

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

Wenn Sie ein Array von Zeigern auf die Strukturen wünschen, verwenden Sie Folgendes:

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

Wenn Sie dann Strukturen in das Array zuweisen möchten, würden Sie Folgendes tun:

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 erlaubt es Ihnen nicht, ein Array einem anderen zuzuweisen. Sie müssen stattdessen eine Schleife verwenden, um jedes Element eines Arrays einem Element des anderen zuzuweisen.

EDIT: Ein weiterer interessanter Ansatz

Ein anderer Ansatz wäre ein eher objektorientierter Ansatz, bei dem Sie einige Dinge kapseln. Zum Beispiel erstellen wir zwei Typen, indem wir dieselben Ebenen von Typen verwenden:

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;

Als nächstes haben wir eine Hilfsfunktion, die wir verwenden, um das Objekt zu erstellen, das angemessen genug benannt ist 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;
}

Jetzt können wir unser neues Objekt wie im Quellcodesegment unten verwenden. Es sollte überprüfen, ob der von CreateTestData() zurückgegebene Zeiger gültig ist, aber das ist wirklich nur, um zu zeigen, was getan werden könnte.

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

In einer wirklich dynamischen Umgebung möchten Sie vielleicht auch eine ReallocTestData(PTestData p) Funktion, die a neu zuweisen würde TestData Objekt, um die Größe des im Objekt enthaltenen Arrays zu ändern.

Wenn Sie mit diesem Ansatz mit einem bestimmten TestData-Objekt fertig sind, können Sie das Objekt einfach wie in freigeben free (go) und das Objekt und sein Array werden gleichzeitig freigegeben.

Bearbeiten: Weiter erweitern

Mit diesem gekapselten Typ können wir jetzt ein paar andere interessante Dinge tun. Zum Beispiel können wir eine Kopierfunktion haben, PTestType CreateCopyTestData (PTestType pSrc) was eine neue Instanz erstellen und dann das Argument in ein neues Objekt kopieren würde. Im folgenden Beispiel verwenden wir die Funktion wieder PTestType CreateTestData (int nCount) Dadurch wird eine Instanz unseres Typs erstellt, wobei die Größe des zu kopierenden Objekts verwendet wird. Nachdem wir das neue Objekt erstellt haben, erstellen wir eine Kopie der Daten aus dem Quellobjekt. Der letzte Schritt besteht darin, den Zeiger, der im Quellenobjekt auf seinen Datenbereich zeigt, so zu reparieren, dass der Zeiger im neuen Objekt jetzt auf den Datenbereich von sich selbst zeigt und nicht auf den Datenbereich des alten Objekts.

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

Benutzeravatar von wildplasser
Wildpässer

Strukturen unterscheiden sich nicht sehr von anderen Objekten. Beginnen wir mit Zeichen:

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

*p ist ein Zeichen, also sizeof *p ist sizeof (char) == 1; wir haben CNT-Zeichen zugewiesen. Nächste:

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

*p ist ein Zeiger auf ein Zeichen, also sizeof *pp ist sizeof (char*). Wir haben CNT-Zeiger zugewiesen. Nächste:

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

*p ist ein struct irgendetwas, also sizeof *p ist sizeof (etwas strukturieren). Wir haben CNT-Strukturen zugewiesen. Nächste:

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

*pp ist ein Zeiger auf eine Struktur, also sizeof *pp ist sizeof (struct etwas*). Wir haben CNT-Zeiger zugewiesen.

1394240cookie-checkC: Zeiger auf Array von Zeigern auf Strukturen (Zuweisungs-/Zuweisungsaufhebungsprobleme)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy