it-swarm.com.de

Ist dies ein guter Weg, um Speicher freizugeben?

Die Funktion zum Freigeben einer Instanz von struct Foo ist unten angegeben: 

void DestroyFoo(Foo* foo)
{
    if (foo) free(foo);
}

Ein Kollege von mir schlug stattdessen Folgendes vor:

void DestroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL; // prevents future concurrency problems
    memset(tmpFoo, 0, sizeof(Foo));  // problems show up immediately if referred to free memory
    free(tmpFoo);
}

Ich sehe, dass es besser ist, den Zeiger auf NULL zu setzen, aber ich bin mir nicht sicher über Folgendes:

  1. Müssen wir den Zeiger wirklich einem temporären zuweisen? Hilft es in Bezug auf Parallelität und Shared Memory?

  2. Ist es wirklich eine gute Idee, den gesamten Block auf 0 zu setzen, um das Programm zum Absturz zu bringen oder zumindest Ergebnisse mit erheblichen Diskrepanzen auszugeben?

55

Müssen wir den Zeiger wirklich einem temporären Zeiger zuweisen? Hilft es in Bezug auf Parallelität und Shared Memory?

Es hat nichts mit Parallelität oder gemeinsamem Speicher zu tun. Es hat keinen Sinn.

Ist es wirklich eine gute Idee, den gesamten Block auf 0 zu setzen, um das Programm zum Absturz zu bringen oder zumindest Ergebnisse mit erheblichen Abweichungen auszugeben?

Nein überhaupt nicht.

Die von Ihrem Kollegen vorgeschlagene Lösung ist schrecklich. Hier ist der Grund:

  • Das Setzen des gesamten Blocks auf 0 führt ebenfalls zu nichts. Da jemand versehentlich einen free () 'ed -Block verwendet, würde er das aufgrund der Werte am Block nicht wissen. Das ist die Art von Block, den calloc() zurückgibt. Es ist also unmöglich zu wissen, ob es sich um frisch zugewiesenen Speicher (calloc() oder malloc()+memset()) handelt oder um den Speicher, der zuvor von Ihrem Code freigegeben () wurde. Wenn überhaupt, ist es zusätzliche Arbeit für Ihr Programm, jeden Speicherblock auf Null zu setzen, der frei ist () 'ed.

  • free(NULL); ist genau definiert und keine Operation, sodass die if-Bedingung in if(ptr) {free(ptr);} nichts bewirkt.

  • Da free(NULL); no-op ist, würde das Setzen des Zeigers auf NULL diesen Fehler tatsächlich verbergen, da eine Funktion tatsächlich free() auf einem bereits freien () 'aufruft. ed Zeiger, dann würden sie das nicht wissen.

  • die meisten Benutzerfunktionen würden zu Beginn eine NULL-Prüfung haben und möglicherweise nicht in Betracht ziehen, NULL als Fehlerbedingung an sie zu übergeben:

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

All diese zusätzlichen Prüfungen und Nullen geben also ein falsches Gefühl von "Robustheit", während es nichts wirklich verbessert hat. Es hat nur ein Problem durch ein anderes ersetzt, nämlich die zusätzlichen Kosten für Leistung und Code Bloat.

Der Aufruf von free(ptr); ohne Wrapper-Funktion ist einfach und robust (die meisten malloc() -Implementierungen würden bei double free sofort abstürzen, was eine gute Sache ist).

Es gibt keinen einfachen Weg, um "versehentlich" free() zweimal oder öfter aufzurufen. Es liegt in der Verantwortung des Programmierers, den gesamten zugewiesenen Speicher zu verfolgen und free() entsprechend zu konfigurieren. Wenn jemand Probleme damit hat, ist C wahrscheinlich nicht die richtige Sprache für ihn.

67
P.P.

Was Ihr Kollege vorschlägt, macht den Code "sicherer", falls die Funktion zweimal aufgerufen wird (siehe Kommentar dazu ... als "sicherer" bedeutet dies möglicherweise nicht für alle gleich ... ;-).

Mit Ihrem Code wird dies höchstwahrscheinlich abstürzen:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
DestroyFoo(foo); // will call free on memory already freed

Mit der Version des Codes Ihrer Kollegen stürzt dies nicht ab:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(&foo);
DestroyFoo(&foo); // will have no effect

Für dieses spezielle Szenario reicht es aus, tmpFoo = 0; (innerhalb von DestroyFoo) auszuführen. memset(tmpFoo, 0, sizeof(Foo)); verhindert den Absturz, wenn Foo zusätzliche Attribute hat, auf die möglicherweise nach der Freigabe des Speichers falsch zugegriffen wird.

Also würde ich ja sagen, es ist eine gute Praxis, dies zu tun ... aber es ist nur eine Art Sicherheit gegen Entwickler, die schlechte Praktiken haben (da es definitiv keinen Grund gibt, DestroyFoo zweimal aufzurufen, ohne sie neu zuzuordnen... am Ende machen Sie DestroyFoo "sicherer", aber langsamer (es wird mehr getan, um eine schlechte Verwendung zu verhindern). 

9
jpo38

Die zweite Lösung scheint übertrieben zu sein. In manchen Situationen ist es natürlich sicherer, aber der Aufwand und die Komplexität sind einfach zu groß.

Wenn Sie auf der sicheren Seite sein möchten, sollten Sie den Zeiger auf NULL setzen, nachdem Sie Speicher freigegeben haben. Dies ist immer eine gute Praxis. 

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;

Außerdem weiß ich nicht, warum die Leute vor dem Aufruf von free () überprüfen, ob der Zeiger NULL ist. Dies ist nicht erforderlich, da free () die Arbeit für Sie erledigen wird.

Das Festlegen des Speichers auf 0 (oder etwas anderes) ist nur in einigen Fällen eine bewährte Methode, da free () den Speicher nicht löscht. Es wird nur einen Speicherbereich als frei markiert, damit er wiederverwendet werden kann. Wenn Sie den Speicher löschen möchten, damit niemand ihn lesen kann, müssen Sie ihn manuell bereinigen. Dies ist jedoch eine recht schwere Operation, und deshalb sollte nicht der gesamte Speicher freigegeben werden. In den meisten Fällen genügt das Freigeben ohne Löschen nur, und Sie müssen nicht auf die Leistung verzichten, um unnötige Operationen auszuführen.

4
codewarrior
void destroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL;
    memset(tmpFoo, 0, sizeof(Foo));
    free(tmpFoo);
}

Ihr Kollegencode ist schlecht, weil

  • es wird abstürzen, wenn fooNULL ist
  • es ist sinnlos, zusätzliche Variablen zu erstellen
  • es macht keinen Sinn, die Werte auf Nullen zu setzen
  • das direkte Freigeben einer Struktur funktioniert nicht, wenn sie Elemente enthält, die freigegeben werden müssen

Ich denke, was Ihr Kollege im Sinn haben könnte, ist dieser Anwendungsfall

Foo* a = NULL;
Foo* b = createFoo();

destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);

In diesem Fall sollte es so sein. Versuch es hier

void destroyFoo(Foo** foo)
{
    if (!foo || !(*foo)) return;
    free(*foo);
    *foo = NULL;
}

Zuerst müssen wir uns Foo ansehen, nehmen wir an, dass es so aussieht

struct Foo
{
    // variables
    int number;
    char character;

    // array of float
    int arrSize;
    float* arr;

    // pointer to another instance
    Foo* myTwin;
};

Um zu definieren, wie es zerstört werden soll, definieren wir zunächst, wie es erstellt werden soll

Foo* createFoo (int arrSize, Foo* twin)
{
    Foo* t = (Foo*) malloc(sizeof(Foo));

    // initialize with default values
    t->number = 1;
    t->character = '1';

    // initialize the array
    t->arrSize = (arrSize>0?arrSize:10);
    t->arr = (float*) malloc(sizeof(float) * t->arrSize);

    // a Foo is a twin with only one other Foo
    t->myTwin = twin;
    if(twin) twin->myTwin = t;

    return t;
}

Jetzt können wir eine Zerstörungsfunktion gegen die Erstellungsfunktion schreiben

Foo* destroyFoo (Foo* foo)
{
    if (foo)
    {
        // we allocated the array, so we have to free it
        free(t->arr);

        // to avoid broken pointer, we need to nullify the twin pointer
        if(t->myTwin) t->myTwin->myTwin = NULL;
    }

    free(foo);

    return NULL;
}

Test hier versuchen

int main ()
{
    Foo* a = createFoo (2, NULL);
    Foo* b = createFoo (4, a);

    a = destroyFoo(a);
    b = destroyFoo(b);

    printf("success");
    return 0;
}
1
Khaled.K

Leider funktioniert diese Idee einfach nicht.

Wenn die Absicht bestand, doppelt gefangen zu werden, deckt dies nicht die folgenden Fälle ab.

Nehmen Sie diesen Code an:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
free (ptr_1);
free (ptr_2); /* This is a bug */

Der Vorschlag lautet stattdessen zu schreiben:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
DestroyFoo (&ptr_1);
DestroyFoo (&ptr_2); /* This is still a bug */

Das Problem ist, dass der zweite Aufruf von DestroyFoo() immer noch abstürzt, da ptr_2 nicht auf NULL zurückgesetzt wird und immer noch auf den bereits freigegebenen Speicher verweist.

0
Marc Alff