17 Stimmen

wann ein Zeiger in C freigegeben wird und wie man weiß, ob er freigegeben ist

Ich bin neu in C und versuche herauszufinden, über Speicherzuweisung in C, dass ich irgendwie verwirrt

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

typedef struct
{
    int a;
} struct1_t;

int main()
{
    funct1(); //init pointer
    return 1;
}

int funct2(struct1_t *ptr2struct)
{
    printf("print a is %d\n",ptr2struct->a);
    //free(ptr2struct);
    printf("value of ptr in funct2 is %p\n", ptr2struct);
    return 1; //success
}

int funct1(){

    struct1_t *ptr2struct = NULL;
    ptr2struct = malloc(sizeof(*ptr2struct));
    ptr2struct->a = 5;
    printf("value of ptr before used is %p", ptr2struct);
    if (funct2(ptr2struct) == 0) {
        goto error;
    }
    free(ptr2struct);

    printf("value of ptr in funct1 after freed is is %p\n", ptr2struct);
    return 1;

error:
    if(ptr2struct) free(ptr2struct);
    return 0;
}

Ich habe eine Funktion 1, die Funktion 2 aufruft, und nachdem ich den zugewiesenen Zeiger in Funktion 1 verwendet habe, versuche ich, den Zeiger freizugeben. Und ich erstelle einen Fall, in dem, wenn der Rückgabewert in funct2 nicht 1 ist, dann versuche ich erneut, den Zeiger zu befreien.

Meine Frage lautet wie folgt

welche Praxis ist besser, wenn ich den Speicher in funct2 freigeben sollte (nachdem ich ihn übergeben habe) oder in funct1 (nachdem ich den Rückgabewert von funct1 erhalten habe) Die zweite Sache ist, ob es richtig ist, einen goto-Fehler zu machen, und Fehler:

if(ptr2struct) free(ptr2struct); 

Meine dritte Frage ist, wie kann ich überprüfen, ob der zugewiesene Wert bereits freigegeben ist oder nicht? denn nachdem ich den Rückgabewert erhalten habe, gebe ich den Zeiger frei, aber wenn ich ihn ausdrucke, zeigt er die gleiche Position mit dem zugewiesenen (also nicht ein Nullzeiger).

19voto

MK. Punkte 32324

Der Aufruf von free() auf einen Zeiger ändert diesen nicht, sondern markiert nur den Speicher als frei. Ihr Zeiger zeigt immer noch auf dieselbe Stelle, die denselben Wert enthält, aber dieser Wert kann nun jederzeit überschrieben werden, so dass Sie einen Zeiger niemals verwenden sollten, nachdem er freigegeben wurde. Um dies sicherzustellen, ist es eine gute Idee, den Zeiger immer auf NULL zu setzen, nachdem er freigegeben wurde.

14voto

hugomg Punkte 65700

1) Sollte ich sie in der aufrufenden Funktion oder in der aufgerufenen Funktion freigeben?

Ich versuche, das Freigeben in der gleichen Funktion durchzuführen, die auch das Mallocing vornimmt. Dadurch bleibt die Speicherverwaltung an einem Ort und bietet auch eine bessere Trennung der Anliegen, da die aufgerufene Funktion in diesem Fall auch mit Zeigern arbeiten kann, die nicht malloziert wurden, oder denselben Zeiger zweimal verwenden kann (wenn Sie das tun möchten).

2) Ist es richtig, einen "goto error" zu machen?

Ja! Indem Sie am Ende der Funktion an eine einzige Stelle springen, vermeiden Sie, dass Sie den ressourcenfreisetzenden Code duplizieren müssen. Dies ist ein gängiges Muster und gar nicht so schlimm, da das "goto" nur als eine Art "Return"-Anweisung dient und keine der wirklich kniffligen und bösen Dinge tut, für die es eher bekannt ist.

//in the middle of the function, whenever you would have a return statement
// instead do
return_value = something;
goto DONE;

//...

DONE:
    //resorce management code all in one spot
    free(stuff);
    return return_value;

C++ hingegen bietet eine elegante Möglichkeit, diese Art der Ressourcenverwaltung durchzuführen. Da Destruktoren deterministisch direkt vor dem Beenden einer Funktion aufgerufen werden, können sie verwendet werden, um diese Art der Ressourcenverwaltung sauber zu verpacken. Sie nennen diese Technik RAII

Eine andere Art und Weise, wie andere Sprachen damit umgehen, sind schließlich Blöcke.

3) Kann ich sehen, ob ein Zeiger bereits freigegeben wurde?

Leider können Sie das nicht. Manche Leute setzen den Wert der Zeigervariablen auf NULL, nachdem sie sie freigegeben haben. Das schadet nicht (da der alte Wert nach dem Freigeben ohnehin nicht mehr verwendet werden sollte) und hat die nette Eigenschaft, dass das Freigeben eines Null-Zeigers als No-Op spezifiziert ist.

Dies ist jedoch nicht narrensicher. Seien Sie vorsichtig, wenn Sie andere Variablen haben, die denselben Zeiger aliasingieren, da sie immer noch den alten Wert enthalten, der jetzt ein gefährlicher Dangling Pointer ist.

1voto

Aditya Sehgal Punkte 2809

Meine Frage lautet wie folgt

welche Praxis ist besser, wenn ich den Speicher in funct2 freigeben sollte (nachdem ich ihn übergeben habe) oder in funct1 (nachdem ich den Rückgabewert von funct1 erhalten habe)

Dies ist eine Frage des "Eigentums". Wem gehört der zugewiesene Speicher. In der Regel muss dies auf der Grundlage des Entwurfs Ihres Programms entschieden werden. Zum Beispiel könnte der einzige Zweck von func1() darin bestehen, nur Speicher zuzuweisen. Das heißt, in Ihrer Implementierung ist func1() die Funktion für die Speicherzuweisung und die "aufrufende" Funktion verwendet den Speicher. In diesem Fall liegt die Verantwortung für die Freigabe des Speichers beim Aufrufer von func1 und NICHT bei func1().

Die zweite Frage ist, ob dies korrekt ist. Die Verwendung von "goto" ist allgemein verpönt. Es verursacht Unordnung im Code, die einfach vermieden werden könnte. Ich sage jedoch "im Allgemeinen". Es gibt Fälle, in denen goto sehr praktisch und nützlich sein kann. In großen Systemen ist zum Beispiel die Konfiguration des Systems ein großer Schritt. Stellen Sie sich vor, Sie rufen eine einzige Config()-Funktion für das System auf, die an verschiedenen Stellen in der Funktion Speicher für die verschiedenen Datenstrukturen alloziert, z.B.

   config()
     {
       ...some config code...
       if ( a specific feature is enabled)
       {
         f1 = allocateMemory();
         level = 1;
       }
       ....some more code....
       if ( another feature is enabled)
       {
         f2 = allocateMemory();
         level = 2;
       }

       ....some more codee....
      if ( another feature is enabled)
      {
        f3 = allocateMemor();
        level =3;
      }

      /*some error happens */
       goto level_3;

     level_3: 
         free(f3);
     level_2:
         free(f2);
     level_1:
         free(f1);
}

In diesem Fall können Sie goto verwenden und auf elegante Weise nur so viel Speicher freigeben, wie bis zu dem Zeitpunkt, an dem die Konfiguration fehlgeschlagen ist, zugewiesen wurde.

Es genügt jedoch zu sagen, dass in Ihrem Beispiel goto leicht vermeidbar ist und vermieden werden sollte.

Meine dritte Frage ist, wie kann ich überprüfen, ob der zugewiesene Wert bereits freigegeben ist oder nicht? denn nachdem ich den Rückgabewert erhalten habe, gebe ich den Zeiger frei, aber wenn ich ihn ausdrucke, zeigt er die gleiche Position mit dem zugewiesenen (also nicht ein Nullzeiger).

Einfach. Setzen Sie den freigegebenen Speicher auf NULL. Der andere Vorteil, abgesehen von dem von MK erwähnten, ist, dass die Übergabe eines NULL-Zeigers an free zu einem NOP führt, d.h. es wird keine Operation durchgeführt. Dies hilft Ihnen auch, Probleme mit doppelten Löschvorgängen zu vermeiden.

1voto

Ahmed Masud Punkte 20604

Was ich hier mit Ihnen teilen werde, sind meine eigenen Entwicklungspraktiken in C. Sie sind keineswegs die einzige Möglichkeit, sich zu organisieren. Ich skizziere nur a Weg nicht die Weise.

Okay, in vielerlei Hinsicht ist "C" eine lockere Sprache, so dass man als Entwickler viel Disziplin und Strenge aufbringen muss. Ich entwickle seit mehr als 20 Jahren beruflich in "C" und musste nur sehr selten eine von mir entwickelte produktionsreife Software korrigieren. Ein großer Teil des Erfolges mag auf Erfahrung zurückzuführen sein, aber ein großer Teil liegt in der konsequenten Praxis begründet.

Ich befolge eine Reihe von Entwicklungspraktiken, die recht umfangreich sind und sich mit so trivialen Dingen wie Registerkarten, Namenskonventionen und anderem befassen. Ich werde mich darauf beschränken, was ich im Umgang mit Strukturen im Allgemeinen und der Speicherverwaltung im Besonderen tue.

  • Wenn ich eine Struktur habe, die in der gesamten Software verwendet wird, schreibe ich für sie Funktionen vom Typ create/destroy; init/done:

     struct foo * init_foo(); 
     void done_foo(struct foo *);

    und die Zuweisung und Freigabe der Struktur in diesen Funktionen.

  • Wenn ich ein Strukturelement direkt im ganzen Programm manipuliere, dann typisiere ich es nicht. Ich mache mir die Mühe, in jeder Deklaration das Schlüsselwort struct zu verwenden, damit ich weiß, dass es sich um eine Struktur handelt. Das reicht, wenn die Schmerzgrenze NICHT so hoch ist, dass ich mich darüber ärgern würde :-)

  • Wenn ich feststelle, dass sich die Struktur SEHR wie ein Objekt verhält, dann entscheide ich mich dafür, die Strukturelemente STRENG über eine undurchsichtige API zu manipulieren; dann definiere ich ihre Schnittstelle durch Funktionen vom Typ set/get für jedes Element, ich erstelle eine "Vorwärtsdeklaration" in der Header-Datei, die von jedem anderen Teil des Programms verwendet wird, ich erstelle einen undurchsichtigen typedef auf den Zeiger der Struktur und deklariere die tatsächliche Struktur nur in der Struktur-API-Implementierungsdatei.

foo.h:

struct foo;
typedef struct foo foo_t;

void set_e1(foo_t f, int e1);
int  get_ei(foo_t f);

int  set_buf(foo_t f, const char *buf);
char * get_buf_byref(foo_t f)
char * get_buf_byval(foo_t f, char *dest, size_t *dlen);

foo.c:

#include <foo.h>

struct foo {
     int e1; 
     char *buf;
     ...
};

void set_e1(foo_t f, int e1) { 
        f->e1 = e1;
}

int  get_ei(foo_t f) { return f->e1; } 

void set_buf(foo_t f, const char *buf) {
     if ( f->buf ) free ( f->buf );
     f->buf = strdup(buf);
}

char *get_buf_byref(foo_t f) { return f->buf; } 
char *get_buf_byval(foo_t f, char **dest, size_t *dlen) {
    *dlen = snprintf(*dest, (*dlen) - 1, "%s", f->buf); /* copy at most dlen-1 bytes */ 
    return *dest;
}
  • Wenn die zugehörigen Strukturen sehr kompliziert sind, kann es sogar sinnvoll sein, Funktionszeiger direkt in eine Basisstruktur zu implementieren und dann die eigentlichen Manipulatoren in bestimmten Erweiterungen dieser Struktur bereitzustellen.

Sie werden eine starke Ähnlichkeit zwischen dem von mir oben skizzierten Ansatz und der objektorientierten Programmierung feststellen. Es ist so gedacht, dass ...

Wenn Sie Ihre Schnittstellen auf diese Weise sauber halten, spielt es keine Rolle, ob Sie überall Instanzvariablen auf NULL setzen müssen oder nicht. Der Code wird sich hoffentlich einer strafferen Struktur unterwerfen, in der dumme Fehler weniger wahrscheinlich sind.

Ich hoffe, das hilft.

0voto

expreun9 Punkte 1

Ich weiß, dass dies bereits beantwortet wurde, aber, Ich wollte meinen Beitrag leisten. Soweit ich das verstehe, werden beim Aufruf einer Funktion mit Parametern wie hier (dem Zeiger) die Parameter in den Stapel(FILO) .


Daher wird der Zeiger, der an die Funktion übergeben wird, automatisch vom Stapel genommen, ohne dass der Zeiger in funct1() . Daher müssen Sie den Zeiger in funct1() Korrigieren Sie mich, wenn ich falsch liege.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X