malloc für Struktur und Zeiger in C

Lesezeit: 5 Minuten

Benutzeravatar von Pouya
Pouya

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

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

Angenommen, ich möchte einen Vektor y definieren und ihm Speicher zuweisen.

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

Meine Suche im 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 zuweise, einen, während ich Speicher für y und den anderen, während ich Speicher für y-> x zuweise, und es scheint eine Verschwendung von Speicher zu sein. Es wird sehr geschätzt, wenn Sie mich wissen lassen, was Compiler wirklich tun und wie der richtige Weg wäre, um sowohl y als auch y->x zu initialisieren.

Danke im Voraus.

  • Wie von Paxdiablo ausdrücklich betont, geben Sie bitte nicht den Rückgabewert von ein malloc() in C. Ich werde nie verstehen, warum jeder das Bedürfnis verspürt. 🙁

    – abschalten

    8. Februar 2013 um 8:37 Uhr

  • @unwind, vielleicht sind es alte C++-Programmierer, die auf C upgraden 🙂

    – paxdiablo

    8. Februar 2013 um 8:55 Uhr


  • @unwind Wenn ich den nvcc-Compiler von Nvidia für C-Code verwende und das Ergebnis von malloc nicht umwandle, wird ein Fehler ausgegeben.

    – Nubkuchen

    12. Juli 2017 um 19:44 Uhr

  • @ Nubcake Laut dieser Link Das könnte daran liegen, dass nvcc den zugrunde liegenden Compiler im C++-Modus ausführt, da ihre CUDA-Schnittstelle C++ ist. In C erhalten Sie dafür keine Fehler. In C++ void * konvertiert nicht automatisch in andere Zeiger, und die Umwandlung wird benötigt (oder verwenden Sie einfach nicht malloc() in C++ natürlich).

    – abschalten

    15. Juli 2017 um 12:10 Uhr


  • @unwind Ja, das habe ich später herausgefunden 🙂 Ich wollte nur eine Situation angeben, in der es einen Fehler geben würde, wenn Sie das Ergebnis nicht übertragen würden.

    – Nubkuchen

    15. Juli 2017 um 14:36 ​​Uhr

Benutzeravatar von paxdiablo
paxdiablo

Nein, du bist nicht Speicher zuweisen für y->x zweimal.

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

Denk darüber so:

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

Die braucht man eigentlich zwei Zuweisungen (1 und 2), um alles zu speichern, was Sie brauchen.

Außerdem sollte Ihr Typ sein struct Vector *y da es sich um einen Zeiger handelt und Sie den Rückgabewert niemals umwandeln sollten malloc in C.

Es kann bestimmte Probleme verbergen, die Sie nicht verbergen möchten, und C ist perfekt in der Lage, die implizit zu konvertieren void* Rückgabewert an jeden anderen Zeiger.

Und natürlich möchten Sie wahrscheinlich die Erstellung dieser Vektoren kapseln, um ihre Verwaltung zu vereinfachen, z. B. indem Sie Folgendes in einer Header-Datei haben vector.h:

struct Vector {
    double *data;    // Use readable names rather than x/n.
    size_t size;
};

struct Vector *newVector(size_t sz);
void delVector(struct Vector *vector);
//void setVectorItem(struct Vector *vector, size_t idx, double val);
//double getVectorItem(struct Vector *vector, size_t idx);

Dann in vector.chaben Sie die eigentlichen Funktionen zur Verwaltung der Vektoren:

#include "vector.h"

// Atomically allocate a two-layer object. Either both layers
// are allocated or neither is, simplifying memory checking.

struct Vector *newVector(size_t sz) {
    // First, the vector layer.

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

    // Then data layer, freeing vector layer if fail.

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

    // Here, both layers worked. Set size and return.

    vector->size = sz;
    return vector;
}

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 Vektorverwaltung so kapseln, stellen Sie sicher, dass Vektoren entweder vollständig oder gar nicht erstellt werden – ja nein Wahrscheinlichkeit, dass sie halb gebaut sind.

Es ermöglicht Ihnen auch, die zugrunde liegenden Datenstrukturen in Zukunft vollständig zu ändern, ohne Clients zu beeinträchtigen. Zum Beispiel:

  • wenn Sie sie zu spärlichen Arrays machen wollten, um Speicherplatz gegen Geschwindigkeit einzutauschen.
  • wenn Sie möchten, dass die Daten bei jeder Änderung im dauerhaften Speicher gespeichert werden.
  • wenn Sie sicherstellen möchten, dass alle Vektorelemente auf Null initialisiert wurden.
  • wenn Sie die Vektorgröße aus Effizienzgründen von der Vektorkapazität trennen wollten(1).

Sie können bei Bedarf auch weitere Funktionen hinzufügen, z. B. das sichere Festlegen oder Abrufen von Vektorwerten (siehe kommentierten Code in der Kopfzeile).

Beispielsweise könnten Sie (als eine Option) Einstellungswerte außerhalb des gültigen Bereichs stillschweigend ignorieren und Null zurückgeben, wenn Sie diese Werte erhalten. Oder Sie könnten einen Fehler irgendeiner Art melden oder versuchen, den Vektor unter der Decke automatisch zu erweitern(1).


In Bezug auf die Verwendung der Vektoren ist ein einfaches Beispiel wie folgt (sehr einfach) main.c

#include "vector.h"

#include <stdio.h>

int main(void) {
    Vector myvec = newVector(42);
    myvec.data[0] = 2.718281828459;
    delVector(myvec);
}

(1) Dieses Potenzial für einen erweiterbaren Vektor bedarf weiterer Erklärung.

Viele Vektorimplementierungen trennen die Kapazität von der Größe. Ersteres gibt an, wie viele Elemente Sie verwenden können, bevor eine Neuzuweisung erforderlich ist, letzteres ist die tatsächliche Vektorgröße (immer <= die Kapazität).

Beim Erweitern möchten Sie im Allgemeinen so erweitern, dass Sie es nicht viel tun, da dies eine teure Operation sein kann. Sie könnten beispielsweise 5 % mehr hinzufügen, als unbedingt erforderlich ist, damit in einer Schleife, die kontinuierlich ein Element hinzufügt, es nicht für jedes einzelne Element neu zuweisen muss.

  • Zittere vor meiner ASCII-Kunstfertigkeit 🙂

    – paxdiablo

    8. Februar 2013 um 8:32 Uhr


  • Vielen Dank. Wirklich hilfreich.

    – Poya

    8. Februar 2013 um 8:47 Uhr

  • In if(retval == NULL) sollte retval gleich retVal sein

    – Legion Daeth

    17. April 2018 um 19:36 Uhr

  • @paxdiablo Vielen Dank für die klare Erklärung und die wunderbare Idee, eine Funktion zu erstellen. Können Sie den Code bitte um a ergänzen main zeigen, wie man anruft newVector()?

    – MA Bisk

    31. August um 16:08 Uhr


  • @MABisk: fertig. Ich habe auch etwas mehr Erläuterungen zu einigen anderen Aspekten hinzugefügt.

    – paxdiablo

    31. August um 21:39 Uhr

Beim ersten Mal weisen Sie Speicher zu Vectorwas die Variablen bedeutet x,n.

Jedoch x deutet noch nichts brauchbares an.

Deshalb also eine zweite Zuordnung ist ebenfalls erforderlich.

Benutzeravatar von Wernsey
Wernsee

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

Nur einige 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 Speicher für ein Vector-Objekt zu. malloc() gibt einen Zeiger auf den zugewiesenen Speicher zurück, also muss y ein Vektorzeiger sein. In der zweiten Zeile weisen Sie Speicher für ein Array von 10 Doubles zu.

In C brauchen Sie die expliziten Umwandlungen und das Schreiben nicht sizeof *y Anstatt von sizeof(struct Vector) ist besser für die Typsicherheit und spart außerdem Tipparbeit.

Sie können Ihre Struktur neu anordnen und eine einzelne ausführen malloc() so:

struct Vector{    
    int n;
    double x[];
};
struct Vector *y = malloc(sizeof *y + 10 * sizeof(double));

  • “tippen sparen” ist nie ein stichhaltiges Argument für Programmierentscheidungen. Der eigentliche Grund, warum Sie *y nehmen würden, sind Sicherheitsgründe, um sicherzustellen, dass Sie der entsprechenden Variablen so viel Platz wie nötig zuweisen.

    – Ludin

    8. Februar 2013 um 9:19 Uhr


  • @Lundin Ich habe meine Antwort aktualisiert, aber für mich ist das Argument “Typsicherheit”. fast in der gleichen Liga wie das Schreiben if(NULL == foo)

    – Wernsee

    8. Februar 2013 um 9:38 Uhr


  • @Lundin Schreiben sizeof *y hilft Ihnen, Fehler wie das Schreiben zu bekämpfen sizeof(Vector) wenn du meinst sizeof(Matrix). Wie oft machst du solche Fehler? Wie schnell finden und beheben Sie sie, wenn Sie dies tun? Ich stimme zu, dass es die Typsicherheit erhöht, und ich schreibe sizeof *y in meinem eigenen Code, aber es ist fast so paranoid wie das Schreiben if(NULL == foo) um Tippfehler zu vermeiden ==.

    – Wernsee

    8. Februar 2013 um 12:57 Uhr

  • @Wernsey, warum ist struct Vector *y = malloc(sizeof *y); das Gleiche wie struct Vector *y = malloc(sizeof(struct Vector)); Tut *y Dereferenzierung auf den Typ Vector irgendwie?

    – Schmuel Kamensky

    12. Oktober 2020 um 8:55 Uhr


  • @ShmuelKamensky Sie sind funktional gleichwertig. y ist ein Hinweis auf struct Vector Also sizeof *y sagt “Größe dessen, worauf y zeigt”, also sizeof struct Vector.

    – Wernsee

    13. Oktober 2020 um 9:23 Uhr

Benutzeravatar von rajneesh
rajneesh

Wenige Punkte

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

es sollte sein struct Vector *y = (struct Vector*)malloc(sizeof(struct Vector)); seit y hält Zeiger auf struct Vector.

1 malloc() weist nur genügend Speicher zu, um die Vektorstruktur zu halten (die ein Zeiger auf double + int ist)

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

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

Benutzeravatar von oleg_g
oleg_g

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

Benutzeravatar von PQuinn
PQuinn

Sie könnten dies tatsächlich in einem einzigen Malloc tun, indem Sie den Vektor und das Array gleichzeitig 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;

Dies weist den Vektor ‘y’ zu und lässt dann y->x auf die extra zugewiesenen Daten unmittelbar nach der Vektorstruktur zeigen (jedoch im selben Speicherblock).

Wenn eine Größenänderung des Vektors erforderlich ist, sollten Sie dies wie empfohlen mit den beiden Zuordnungen tun. Das interne y->x-Array könnte dann in der Größe geändert werden, während die Vektorstruktur ‘y’ intakt bleibt.

  • Warum haben Sie speziell cast y in char eingegeben? Warum nicht (double*)y + sizeof(struct Vector)?

    – Vishnu Prasath

    24. Februar 2014 um 13:15 Uhr


  • sizeof gibt die Strukturgröße in Bytes zurück, und der arithmetische ‘+’-Operator des Zeigers fügt dem ‘y’-Zeiger ein Vielfaches von sizeof(y). Wenn wir wie oben vorgehen würden, würde y um sizeof(double)*sizeof(struct) erhöht werden, was zu viel ist. Casting von y zu char Lassen Sie uns y um sizeof(char)*sizeof(struct) = 1*sizeof(struct) erhöhen

    – PQuinn

    25. Februar 2014 um 8:16 Uhr

  • Tu das nicht. Das gewährleistet es nicht y->x richtig ausgerichtet ist, um a zu halten double. Wenn dies nicht der Fall ist, haben Sie ein undefiniertes Verhalten.

    – real oder zufällig

    9. November 2021 um 15:27 Uhr

1421330cookie-checkmalloc für Struktur und Zeiger in C

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

Privacy policy