Malloc für Struktur und Zeiger in C.

80

Angenommen, ich möchte eine Struktur definieren, die die Länge des Vektors und seine Werte wie folgt darstellt:

struct Vector{
    double* x;
    int n;
};

Angenommen, ich möchte einen Vektor y definieren und Speicher dafür zuweisen.

struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));

Meine Suche über das Internet zeigt, dass ich den Speicher für x separat zuweisen sollte.

y->x = (double*)malloc(10*sizeof(double));

Aber es scheint, dass ich den Speicher für y-> x zweimal zuordne, einen beim Zuweisen des Speichers für y und den anderen beim Zuweisen des Speichers für y-> x, und es scheint eine Verschwendung von Speicher zu sein. Es wird sehr geschätzt, wenn Sie mich wissen lassen, was der Compiler wirklich tut und wie Sie sowohl y als auch y-> x richtig initialisieren können.

Danke im Voraus.

Pouya
quelle
5
Wie von paxdiablo besonders hervorgehoben, geben Sie bitte nicht den Rückgabewert von malloc()in C ein. Ich werde nie verstehen, warum jeder das für nötig hält. :(
Entspannen Sie sich
14
@unwind, vielleicht sind sie alte C ++ - Programmierer, die auf C
upgraden
@unwind Wenn ich den nvcc-Compiler von Nvidia für C-Code verwende und das Ergebnis von malloc nicht umwandle, wird ein Fehler ausgegeben.
Nubcake
@Nubcake Laut diesem Link kann dies daran liegen, dass nvcc den zugrunde liegenden Compiler im C ++ - Modus ausführt, da die CUDA-Schnittstelle C ++ ist. In C erhalten Sie dafür keine Fehler. In C ++ void *wird nicht automatisch in andere Zeiger konvertiert, und die Umwandlung wird benötigt (oder malloc()natürlich nicht in C ++ verwendet).
Entspannen Sie
@unwind Ja, ich habe es später herausgefunden :) Ich wollte nur eine Situation angeben, in der es einen Fehler auslösen würde, wenn Sie das Ergebnis nicht umsetzen würden.
Nubcake

Antworten:

151

Nein, Sie weisen nichty->x zweimal Speicher zu .

Stattdessen weisen Sie Speicher für die Struktur zu (die einen Zeiger enthält) sowie etwas, auf das dieser Zeiger zeigen soll.

Denk darüber so:

         1          2
        +-----+    +------+
y------>|  x------>|  *x  |
        |  n  |    +------+
        +-----+

Sie benötigen also tatsächlich die beiden Zuordnungen ( 1und 2), um alles zu speichern.

Außerdem sollte Ihr Typ sein, struct Vector *yda es sich um einen Zeiger handelt, und Sie sollten den Rückgabewert niemals mallocin C umwandeln, da er bestimmte Probleme verbergen kann, die nicht ausgeblendet werden sollen. C ist perfekt in der Lage, den void*Rückgabewert implizit in einen anderen Zeiger zu konvertieren .

Und natürlich möchten Sie wahrscheinlich die Erstellung dieser Vektoren kapseln, um die Verwaltung zu vereinfachen, z. B.:

struct Vector {
    double *data;    // no place for x and n in readable code :-)
    size_t size;
};

struct Vector *newVector (size_t sz) {
    // Try to allocate vector structure.

    struct Vector *retVal = malloc (sizeof (struct Vector));
    if (retVal == NULL)
        return NULL;

    // Try to allocate vector data, free structure if fail.

    retVal->data = malloc (sz * sizeof (double));
    if (retVal->data == NULL) {
        free (retVal);
        return NULL;
    }

    // Set size and return.

    retVal->size = sz;
    return retVal;
}

void delVector (struct Vector *vector) {
    // Can safely assume vector is NULL or fully built.

    if (vector != NULL) {
        free (vector->data);
        free (vector);
    }
}

Indem Sie die Erstellung so kapseln, stellen Sie sicher, dass Vektoren entweder vollständig oder gar nicht erstellt werden - es besteht keine Chance, dass sie zur Hälfte erstellt werden. Außerdem können Sie die zugrunde liegenden Datenstrukturen in Zukunft vollständig ändern, ohne die Clients zu beeinträchtigen (z. B. wenn Sie sie zu spärlichen Arrays machen möchten, um Speicherplatz gegen Geschwindigkeit auszutauschen).

paxdiablo
quelle
25
Zittern Sie an meiner ASCII-Kunstfertigkeit :-)
paxdiablo
Vielen Dank. Wirklich hilfreich.
Pouya
1
In if (retval == NULL) sollte retval retVal sein
Legion Daeth
5

Das erste Mal, weisen Sie Speicher für Vector, die die Variablen bedeutet x, n.

Zeigt x jedoch noch nichts Nützliches an .

Deshalb ist auch eine zweite Zuweisung erforderlich .

Karthik T.
quelle
3

Einige Punkte

struct Vector y = (struct Vector*)malloc(sizeof(struct Vector)); ist falsch

es sollte struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector));da yZeiger auf halten struct Vector.

1. malloc()reserviert nur genügend Speicher, um die Vektorstruktur aufzunehmen (was ein Zeiger auf double + int ist)

2. malloc()tatsächlich Speicher zuweisen, um 10 doppelt zu halten.

Rajneesh
quelle
2

Sie können dies tatsächlich in einem einzigen Malloc tun, indem Sie gleichzeitig den Vektor und das Array zuweisen. Z.B:

struct Vector y = (struct Vector*)malloc(sizeof(struct Vector) + 10*sizeof(double));
y->x = (double*)((char*)y + sizeof(struct Vector));
y->n = 10;

Dadurch wird der Vektor 'y' zugewiesen, und y-> x zeigt unmittelbar nach der Vektorstruktur (jedoch im selben Speicherblock) auf die zusätzlich zugewiesenen Daten.

Wenn die Größe des Vektors geändert werden muss, sollten Sie die beiden empfohlenen Zuordnungen verwenden. Die Größe des internen y-> x-Arrays könnte dann geändert werden, während die Vektorstruktur 'y' intakt bleibt.

PQuinn
quelle
Warum haben Sie cast y to char speziell eingegeben? Warum nicht (double *) y + sizeof (struct Vector)?
Vishnu Prasath
sizeof gibt die Strukturgröße in Bytes zurück, und der Zeigerarithmetikoperator '+' fügt dem 'y'-Zeiger ein Vielfaches der Größe von ( y) hinzu. Wenn wir wie oben beschrieben vorgehen würden, würde y um sizeof (double) * sizeof (struct) erhöht, was zu viel ist. Wenn wir y in char umwandeln, können wir y um sizeof (char) * sizeof (struct) = 1 * sizeof (struct)
erhöhen
2

Im Prinzip machst du es schon richtig. Für das, was Sie wollen, brauchen Sie zwei malloc()s.

Nur ein paar Kommentare:

struct Vector y = (struct Vector*)malloc(sizeof(struct Vector));
y->x = (double*)malloc(10*sizeof(double));

sollte sein

struct Vector *y = malloc(sizeof *y); /* Note the pointer */
y->x = calloc(10, sizeof *y->x);

In der ersten Zeile weisen Sie einem Vektorobjekt Speicher zu. malloc()Gibt einen Zeiger auf den zugewiesenen Speicher zurück, daher muss y ein Vektorzeiger sein. In der zweiten Zeile weisen Sie Speicher für ein Array von 10 Doubles zu.

In C benötigen Sie keine expliziten Casts, und das Schreiben sizeof *yanstelle von sizeof(struct Vector)ist besser für die Typensicherheit und spart außerdem Tipparbeit.

Sie können Ihre Struktur neu anordnen und eine Single malloc()wie folgt ausführen :

struct Vector{    
    int n;
    double x[];
};
struct Vector *y = malloc(sizeof *y + 10 * sizeof(double));
Wernsey
quelle
"Spart beim Tippen" ist niemals ein gültiges Argument für Programmierentscheidungen. Der eigentliche Grund, warum Sie * y nehmen würden, ist aus Sicherheitsgründen, um sicherzustellen, dass Sie so viel Speicherplatz wie nötig für die entsprechende Variable zuweisen.
Lundin
@Lundin Ich habe meine Antwort aktualisiert, aber für mich ist das Argument " if(NULL == foo)
Typensicherheit
Du bist derjenige, der predigt zu benutzen sizeof *y. Wenn Sie dies nur tun, um 5 Buchstaben weniger (sizeof * y versus sizeof (Vector)) ohne ein anderes Argument einzugeben, muss dies daran liegen, dass Sie die Aufgabe, 5 Buchstaben auf Ihrer Tastatur einzugeben, als großes Hindernis empfinden. Und wenn ja, dann ziehen Sie vielleicht eine andere Berufswahl in Betracht, da das Programmieren eine Menge Tastatureingaben erfordert ...
Lundin
@Lundin Writing sizeof *yhilft Ihnen dabei, Fehler wie das Schreiben zu bekämpfen, sizeof(Vector)wenn Sie es gemeint haben sizeof(Matrix). Wie oft machst du solche Fehler? Wie schnell finden und beheben Sie sie, wenn Sie dies tun? Ich bin damit einverstanden, dass es die Typensicherheit erhöht, und ich schreibe sizeof *yin meinen eigenen Code, aber es ist fast so paranoid wie das Schreiben if(NULL == foo), um Tippfehler auf dem zu verhindern ==.
Wernsey
1
@ShmuelKamensky Sie sind funktional gleichwertig. yist ein Zeiger auf struct Vectorso sizeof *yheißt "Größe dessen, worauf y zeigt", also sizeof struct Vector.
Wernsey
0

Wenn Sie Speicher für struct VectorSie zuweisen, weisen Sie nur Speicher für Zeiger zu x, dh für Leerzeichen, wo sein Wert, der die Adresse enthält, platziert wird. Auf diese Weise weisen Sie dem Block, auf den y.xverwiesen wird, keinen Speicher zu .

Andremoniy
quelle
0

Das erste malloc weist Speicher für struct zu, einschließlich Speicher für x (Zeiger auf double). Das zweite Malloc weist Speicher für einen doppelten Wert zu, auf den x zeigt.

oleg_g
quelle