Setzen der Variablen nach free auf NULL

156

In meiner Firma gibt es eine Codierungsregel, die besagt, dass die Variable nach dem Freigeben von Speicher auf zurückgesetzt wird NULL. Zum Beispiel ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Ich habe das Gefühl, dass in Fällen wie dem oben gezeigten Code die Einstellung auf NULLkeine Bedeutung hat. Oder fehlt mir etwas?

Wenn es in solchen Fällen keine Bedeutung gibt, werde ich mich mit dem "Qualitätsteam" in Verbindung setzen, um diese Kodierungsregel zu entfernen. Bitte beraten.

Alphaneo
quelle
2
Es ist immer nützlich zu überprüfen, ob, ptr == NULLbevor Sie etwas damit tun. Wenn Sie Ihre freien Zeiger nicht aufheben, erhalten Sie einen ptr != NULLimmer noch unbrauchbaren Zeiger.
Ki Jéy
Baumelnde Zeiger können zu ausnutzbaren Sicherheitslücken wie Use-After-Free führen .
4онстантин Ван

Antworten:

285

Das Setzen nicht verwendeter Zeiger auf NULL ist ein defensiver Stil, der vor baumelnden Zeigerfehlern schützt. Wenn auf einen baumelnden Zeiger zugegriffen wird, nachdem er freigegeben wurde, können Sie zufälligen Speicher lesen oder überschreiben. Wenn auf einen Nullzeiger zugegriffen wird, kommt es auf den meisten Systemen sofort zu einem Absturz, der Sie sofort über den Fehler informiert.

Bei lokalen Variablen kann es etwas sinnlos sein, wenn "offensichtlich" ist, dass nach dem Freigeben nicht mehr auf den Zeiger zugegriffen wird. Daher ist dieser Stil besser für Mitgliedsdaten und globale Variablen geeignet. Selbst für lokale Variablen kann es ein guter Ansatz sein, wenn die Funktion nach der Freigabe des Speichers fortgesetzt wird.

Um den Stil zu vervollständigen, sollten Sie Zeiger auch auf NULL initialisieren, bevor ihnen ein echter Zeigerwert zugewiesen wird.

Martin v. Löwis
quelle
3
Ich verstehe nicht, warum Sie "Zeiger auf NULL initialisieren würden, bevor ihnen ein echter Zeigerwert zugewiesen wird"?
Paul Biggar
26
@ Paul: Im konkreten Fall könnte die Erklärung lauten int *nPtr=NULL;. Jetzt würde ich zustimmen, dass dies überflüssig wäre, wobei ein Malloc direkt in der nächsten Zeile folgt. Wenn sich jedoch zwischen der Deklaration und der ersten Initialisierung Code befindet, kann jemand die Variable verwenden, obwohl sie noch keinen Wert hat. Wenn Sie null initialisieren, erhalten Sie den Segfault. ohne könnten Sie wieder zufälligen Speicher lesen oder schreiben. Wenn die Variable später nur bedingt initialisiert wird, sollten spätere fehlerhafte Zugriffe sofort zu Abstürzen führen, wenn Sie daran gedacht haben, null zu initialisieren.
Martin v. Löwis
1
Ich persönlich denke, dass es in jeder nicht trivialen Codebasis genauso vage ist, einen Fehler beim Dereferenzieren von Null zu erhalten, wie einen Fehler beim Dereferenzieren einer Adresse, die Sie nicht besitzen. Ich persönlich störe nie.
Wilhelmtell
9
Wilhelm, der Punkt ist, dass Sie mit einer Nullzeiger-Dereferenzierung einen bestimmten Absturz und den tatsächlichen Ort des Problems erhalten. Ein schlechter Zugriff kann abstürzen oder nicht und Daten oder Verhalten auf unerwartete Weise an unerwarteten Orten beschädigen.
Amit Naidu
4
Das Initialisieren des Zeigers auf NULL hat mindestens einen wesentlichen Nachteil: Es kann verhindern, dass der Compiler Sie vor nicht initialisierten Variablen warnt. Es sei denn, die Logik Ihres Codes behandelt diesen Wert für den Zeiger explizit (dh wenn (nPtr == NULL) dosomething ...), ist es besser, ihn unverändert zu lassen.
Eric
37

Setzen eines Zeigers auf NULLnachherfree ist eine zweifelhafte Praxis, die häufig als "gute Programmierregel" unter einer offensichtlich falschen Prämisse populär gemacht wird. Es ist eine dieser gefälschten Wahrheiten, die zur Kategorie "klingt richtig" gehören, aber in Wirklichkeit absolut nichts Nützliches erreichen (und manchmal zu negativen Konsequenzen führen).

Angeblich soll das Setzen eines Zeigers auf NULLafter freedas gefürchtete Problem "double free" verhindern, wenn derselbe Zeigerwert an freemehr als einmal übergeben wird. In der Realität tritt jedoch in 9 von 10 Fällen das eigentliche "Double Free" -Problem auf, wenn verschiedene Zeigerobjekte mit demselben Zeigerwert als Argumente für verwendet werden free. Unnötig zu sagen, einen Zeiger auf NULLdanach setzenfree in solchen Fällen absolut nichts bewirkt, um das Problem zu verhindern.

Natürlich ist es möglich, auf ein "doppelt freies" Problem zu stoßen, wenn dasselbe Zeigerobjekt als Argument für verwendet wird free. In der Realität weisen solche Situationen jedoch normalerweise auf ein Problem mit der allgemeinen logischen Struktur des Codes hin, nicht nur auf ein zufälliges "doppelt frei". Ein geeigneter Weg, um das Problem in solchen Fällen zu lösen, besteht darin, die Struktur des Codes zu überprüfen und zu überdenken, um die Situation zu vermeiden, in der derselbe Zeiger freemehr als einmal übergeben wird. In solchen Fällen setzen Sie den Zeiger aufNULL und das Betrachten des Problems als "behoben" nichts anderes als ein Versuch, das Problem unter den Teppich zu kehren. Im Allgemeinen funktioniert es einfach nicht, da das Problem mit der Codestruktur immer einen anderen Weg findet, sich zu manifestieren.

Wenn Ihr Code speziell dafür ausgelegt ist, dass der Zeigerwert vorhanden ist NULLoder nicht NULL, ist es vollkommen in Ordnung, den Zeigerwert auf NULLafter zu setzen free. Aber als allgemeine "Good Practice" -Regel (wie in "Setzen Sie immer Ihren Zeiger auf " NULLNach free") handelt es sich erneut um eine bekannte und ziemlich nutzlose Fälschung, die häufig von einigen aus rein religiösen, voodooähnlichen Gründen befolgt wird.

Ameise
quelle
1
Bestimmt. Ich kann mich nicht erinnern, jemals ein Double-Free verursacht zu haben, das behoben werden würde, indem der Zeiger nach dem Freigeben auf NULL gesetzt wird, aber ich habe viele verursacht, die dies nicht tun würden.
LnxPrgr3
4
@AnT "zweifelhaft" ist ein bisschen viel. Es hängt alles vom Anwendungsfall ab. Wenn der Wert des Zeigers jemals im wahren / falschen Sinne verwendet wird, ist dies nicht nur eine gültige, sondern auch eine bewährte Methode.
Coder
1
@Coder Völlig falsch. Wenn der Wert des Zeigers in einem wahrhaft falschen Sinne verwendet wird, um zu wissen, ob er vor dem Aufruf zum Freigeben auf ein Objekt zeigte oder nicht, ist dies nicht nur keine bewährte Methode, sondern auch falsch . Zum Beispiel : foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Hier Einstellung barzu NULLnach dem Aufruf freebewirkt , dass die Funktion denken , dass es nie eine Bar hatte und den falschen Wert zurück!
David Schwartz
Ich denke nicht, dass der Hauptvorteil darin besteht, sich vor einem Double Free zu schützen, sondern darin, baumelnde Zeiger früher und zuverlässiger zu fangen. Wenn Sie beispielsweise eine Struktur freigeben, die Ressourcen, Zeiger auf zugewiesenen Speicher, Dateihandles usw. enthält, während ich die enthaltenen Speicherzeiger freigebe und die enthaltenen Dateien schließe, NULL ich die entsprechenden Mitglieder. Wenn dann versehentlich über einen baumelnden Zeiger auf eine der Ressourcen zugegriffen wird, neigt das Programm jedes Mal dazu, genau dort Fehler zu machen. Andernfalls werden die freigegebenen Daten ohne NULL möglicherweise noch nicht überschrieben und der Fehler ist möglicherweise nicht leicht reproduzierbar.
Jimhark
1
Ich bin damit einverstanden, dass gut strukturierter Code den Fall nicht zulassen sollte, dass auf einen Zeiger zugegriffen wird, nachdem er freigegeben wurde, oder den Fall, dass er zweimal freigegeben wurde. In der realen Welt wird mein Code jedoch von jemandem geändert und / oder gepflegt, der mich wahrscheinlich nicht kennt und nicht über die Zeit und / oder die Fähigkeiten verfügt, um die Dinge richtig zu machen (da die Frist immer gestern ist). Daher neige ich dazu, kugelsichere Funktionen zu schreiben, die das System auch bei Missbrauch nicht zum Absturz bringen.
Mfloris
34

Die meisten Antworten haben sich darauf konzentriert, ein Double Free zu verhindern, aber das Setzen des Zeigers auf NULL hat einen weiteren Vorteil. Sobald Sie einen Zeiger freigeben, kann dieser Speicher durch einen weiteren Aufruf von malloc neu zugewiesen werden. Wenn Sie immer noch den ursprünglichen Zeiger in der Nähe haben, kann es zu einem Fehler kommen, bei dem Sie versuchen, den Zeiger zu verwenden, nachdem Sie eine andere Variable freigegeben und beschädigt haben. Dann wechselt Ihr Programm in einen unbekannten Zustand und es können alle möglichen schlechten Dinge passieren (Absturz, wenn Sie Ich habe Glück, Datenkorruption, wenn Sie Pech haben. Wenn Sie den Zeiger nach dem Freiwerden auf NULL gesetzt hätten, würde jeder Versuch, diesen Zeiger später zu lesen / schreiben, zu einem Segfault führen, der im Allgemeinen einer zufälligen Speicherbeschädigung vorzuziehen ist.

Aus beiden Gründen kann es eine gute Idee sein, den Zeiger nach free () auf NULL zu setzen. Es ist jedoch nicht immer notwendig. Wenn beispielsweise die Zeigervariable unmittelbar nach free () den Gültigkeitsbereich verlässt, gibt es nicht viel Grund, sie auf NULL zu setzen.

Mike McNertney
quelle
1
+1 Das ist eigentlich ein sehr guter Punkt. Nicht die Argumentation über "doppelt frei" (was völlig falsch ist), sondern diese . Ich bin nicht der Fan des mechanischen NULL-Zeigens von Zeigern danach free, aber das macht tatsächlich Sinn.
AnT
Wenn Sie auf einen Zeiger zugreifen können, nachdem Sie ihn über denselben Zeiger freigegeben haben, ist es noch wahrscheinlicher, dass Sie auf einen Zeiger zugreifen, nachdem Sie das Objekt freigegeben haben, auf das er über einen anderen Zeiger zeigt. Das hilft Ihnen also überhaupt nicht - Sie müssen immer noch einen anderen Mechanismus verwenden, um sicherzustellen, dass Sie nicht über einen Zeiger auf ein Objekt zugreifen, nachdem Sie es über einen anderen freigegeben haben. Sie können diese Methode auch verwenden, um im selben Zeigerfall zu schützen.
David Schwartz
1
@ DavidSchwartz: Ich bin mit Ihrem Kommentar nicht einverstanden. Als ich vor einigen Wochen einen Stapel für eine Universitätsübung schreiben musste, hatte ich ein Problem, ich habe ein paar Stunden nachgeforscht. Ich habe irgendwann auf einen bereits freigegebenen Speicher zugegriffen (der freie war einige Zeilen zu früh). Und manchmal führt es zu sehr seltsamem Verhalten. Wenn ich den Zeiger nach dem Freigeben auf NULL gesetzt hätte, wäre ein "einfacher" Segfault aufgetreten, und ich hätte ein paar Stunden Arbeit gespart. Also +1 für diese Antwort!
Mozzbozz
2
@katze_sonne Sogar eine angehaltene Uhr ist zweimal am Tag richtig. Es ist viel wahrscheinlicher, dass durch das Setzen von Zeigern auf NULL Fehler ausgeblendet werden , indem verhindert wird , dass fehlerhafte Zugriffe auf bereits freigegebene Objekte in Code fehlerhaft sind , der nach NULL sucht und dann ein Objekt, das überprüft werden sollte, stillschweigend nicht überprüft. (Vielleicht ist es hilfreich, Zeiger in bestimmten Debug-Builds auf NULL zu setzen, nachdem sie frei sind, oder sie auf einen anderen Wert als NULL zu setzen, der garantiert segfault ist. Aber dass diese Dummheit Ihnen einmal geholfen hat, ist kein Argument für sie .)
David Schwartz
@ DavidSchwartz Nun, das klingt vernünftig ... Danke für deinen Kommentar, ich werde dies in Zukunft berücksichtigen! :) +1
Mozzbozz
20

Dies wird als bewährte Methode angesehen, um ein Überschreiben des Speichers zu vermeiden. In der obigen Funktion ist dies nicht erforderlich, aber wenn dies erledigt ist, kann es häufig Anwendungsfehler finden.

Versuchen Sie stattdessen so etwas:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

Mit DEBUG_VERSION können Sie Profilfreigaben im Debugging-Code erstellen, aber beide sind funktional gleich.

Bearbeiten : Hinzugefügt do ... während wie unten vorgeschlagen, danke.

razzed
quelle
3
Die Makroversion hat einen subtilen Fehler, wenn Sie sie nach einer if-Anweisung ohne Klammern verwenden.
Mark Ransom
Was ist mit der (nichtigen) 0? Dieser Code lautet: if (x) myfree (& x); sonst do_foo (); wird wenn (x) {frei (* (& x)); * (& x) = null; } void 0; sonst do_foo (); Das andere ist ein Fehler.
jmucchiello
Dieses Makro ist ein perfekter Ort für den Kommaoperator: free ( (p)), * (p) = null. Das nächste Problem ist natürlich, dass * (p) zweimal ausgewertet wird. Es sollte {void * _pp = (p) sein; Freies P & P); * _pp = null; } Macht der Präprozessor nicht Spaß?
jmucchiello
5
Das Makro sollte nicht in bloßen Klammern stehen, sondern in einem do { } while(0)Block, damit if(x) myfree(x); else dostuff();es nicht kaputt geht.
Chris Lutz
3
Wie Lutz sagte, ist der Makrokörper do {X} while (0)IMO der beste Weg, um einen Makrokörper zu erstellen, der sich wie eine Funktion anfühlt und funktioniert. Die meisten Compiler optimieren die Schleife trotzdem.
Mike Clark
7

Wenn Sie den Zeiger erreichen, der frei war () d, kann er brechen oder nicht. Dieser Speicher wird möglicherweise einem anderen Teil Ihres Programms zugewiesen, und Sie erhalten eine Speicherbeschädigung.

Wenn Sie den Zeiger auf NULL setzen und dann darauf zugreifen, stürzt das Programm immer mit einem Segfault ab. Nicht mehr, manchmal funktioniert es, nicht mehr, stürzt auf unvorhersehbare Weise ab. Das Debuggen ist viel einfacher.

Tadeusz A. Kadłubowski
quelle
5
Das Programm stürzt nicht immer mit einem Segfault ab. Wenn die Art und Weise, wie Sie auf den Zeiger zugreifen, bedeutet, dass vor der Dereferenzierung ein ausreichend großer Versatz auf ihn angewendet wird, erreicht er möglicherweise den adressierbaren Speicher: ((MyHugeStruct *) 0) -> fieldNearTheEnd. Und das noch bevor Sie sich mit Hardware befassen, die bei 0-Zugriff überhaupt keinen Fehler verursacht. Es ist jedoch wahrscheinlicher, dass das Programm mit einem Segfault abstürzt.
Steve Jessop
7

Das Setzen des Zeigers auf den free'd-Speicher bedeutet, dass jeder Versuch, über den Zeiger auf diesen Speicher zuzugreifen, sofort abstürzt, anstatt undefiniertes Verhalten zu verursachen. Es macht es viel einfacher festzustellen, wo etwas schief gelaufen ist.

Ich kann Ihr Argument sehen: Da nPtres unmittelbar danach aus dem Rahmen nPtr = NULLgeht, scheint es keinen Grund zu geben, es festzulegen NULL. Im Fall eines structMitglieds oder an einem anderen Ort, an dem der Zeiger nicht sofort den Gültigkeitsbereich verlässt, ist dies jedoch sinnvoller. Es ist nicht sofort ersichtlich, ob dieser Zeiger von Code, der ihn nicht verwenden sollte, erneut verwendet wird oder nicht.

Es ist wahrscheinlich, dass die Regel angegeben wird, ohne zwischen diesen beiden Fällen zu unterscheiden, da es viel schwieriger ist, die Regel automatisch durchzusetzen, geschweige denn, dass die Entwickler sie befolgen. Es tut nicht weh, NULLnach jedem Frei Zeiger zu setzen , aber es hat das Potenzial, auf große Probleme hinzuweisen.

Jared Oberhaus
quelle
7

Der häufigste Fehler in c ist das Double Free. Grundsätzlich machst du so etwas

free(foobar);
/* lot of code */
free(foobar);

und es endet ziemlich schlecht, das Betriebssystem versucht, etwas bereits freigegebenen Speicher freizugeben und im Allgemeinen segfault es. Die bewährte Methode ist also, auf zu setzen NULL, damit Sie testen und prüfen können, ob Sie diesen Speicher wirklich freigeben müssen

if(foobar != null){
  free(foobar);
}

free(NULL)Beachten Sie auch, dass dies nichts bewirkt, sodass Sie die if-Anweisung nicht schreiben müssen. Ich bin nicht wirklich ein OS-Guru, aber ich bin ziemlich hübsch, selbst jetzt würden die meisten OSes doppelt doppelt abstürzen.

Dies ist auch ein Hauptgrund, warum alle Sprachen mit Garbage Collection (Java, Dotnet) so stolz darauf waren, dieses Problem nicht zu haben und auch nicht die gesamte Speicherverwaltung den Entwicklern überlassen zu müssen.

RageZ
quelle
11
Sie können tatsächlich einfach free () aufrufen, ohne zu überprüfen - free (NULL) ist definiert als nichts tun.
Amber
5
Versteckt das nicht Fehler? (Als würde man zu viel befreien.)
Georg Schölly
1
Danke, ich habe es verstanden. Ich habe versucht:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang
5
Wie gesagt, free(void *ptr) kann den Wert des übergebenen Zeigers nicht ändern. Es kann den Inhalt des Zeigers, die an dieser Adresse gespeicherten Daten , aber nicht die Adresse selbst oder den Wert des Zeigers ändern . Das würde erfordern free(void **ptr)(was anscheinend vom Standard nicht erlaubt ist) oder ein Makro (was erlaubt und perfekt portabel ist, aber die Leute mögen keine Makros). Außerdem geht es bei C nicht um Bequemlichkeit, sondern darum, Programmierern so viel Kontrolle zu geben, wie sie wollen. Wenn sie nicht möchten, dass der zusätzliche Aufwand für das Setzen von Zeigern erhöht wird NULL, sollte er ihnen nicht aufgezwungen werden.
Chris Lutz
2
Es gibt nur wenige Dinge auf der Welt, die den Mangel an Professionalität seitens des C-Code-Autors verraten. Dazu gehört jedoch "Überprüfen des Zeigers auf NULL vor dem Aufruf free" (zusammen mit Dingen wie "Umwandeln des Ergebnisses von Speicherzuweisungsfunktionen" oder "gedankenloses Verwenden von Typnamen mit sizeof").
AnT
6

Die Idee dahinter ist, die versehentliche Wiederverwendung des freigegebenen Zeigers zu stoppen.

Mitch Wheat
quelle
4

Dies kann tatsächlich wichtig sein. Obwohl Sie den Speicher freigeben, könnte ein späterer Teil des Programms etwas Neues zuweisen, das zufällig im Raum landet. Ihr alter Zeiger würde jetzt auf einen gültigen Speicherblock verweisen. Es ist dann möglich, dass jemand den Zeiger verwendet, was zu einem ungültigen Programmstatus führt.

Wenn Sie den Zeiger auf NULL setzen, wird jeder Versuch, ihn zu verwenden, 0x0 dereferenzieren und genau dort abstürzen, was leicht zu debuggen ist. Zufällige Zeiger, die auf zufälligen Speicher verweisen, sind schwer zu debuggen. Es ist offensichtlich nicht notwendig, aber deshalb steht es in einem Best-Practice-Dokument.

Steven Canfield
quelle
Zumindest unter Windows setzen Debug-Builds den Speicher auf 0xdddddddd. Wenn Sie also einen Zeiger auf gelöschten Speicher verwenden, den Sie sofort kennen. Auf allen Plattformen sollten ähnliche Mechanismen vorhanden sein.
i_am_jorf
2
jeffamaphone, gelöschter Speicherblock wurde möglicherweise neu zugewiesen und einem anderen Objekt zugewiesen, wenn Sie den Zeiger erneut verwenden.
Constantin
4

Aus dem ANSI C-Standard:

void free(void *ptr);

Die freie Funktion bewirkt, dass der Speicherplatz, auf den ptr zeigt, freigegeben wird, dh für die weitere Zuweisung verfügbar gemacht wird. Wenn ptr ein Nullzeiger ist, wird keine Aktion ausgeführt. Andernfalls ist das Verhalten undefiniert, wenn das Argument nicht mit einem zuvor von der Funktion calloc, malloc oder realloc zurückgegebenen Zeiger übereinstimmt oder wenn der Speicherplatz durch einen Aufruf von free oder realloc freigegeben wurde.

"das undefinierte Verhalten" ist fast immer ein Programmabsturz. Um dies zu vermeiden, ist es sicher, den Zeiger auf NULL zurückzusetzen. free () selbst kann dies nicht tun, da nur ein Zeiger übergeben wird, kein Zeiger auf einen Zeiger. Sie können auch eine sicherere Version von free () schreiben, die den Zeiger NULL macht:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
Vijay Mathew
quelle
@DrPizza - Ein Fehler (meiner Meinung nach) führt dazu, dass Ihr Programm nicht so funktioniert, wie es soll. Wenn ein verstecktes Double Free Ihr Programm bricht, ist dies ein Fehler. Wenn es genau so funktioniert, wie es beabsichtigt war, ist es kein Fehler.
Chris Lutz
@ DrPizza: Ich habe gerade ein Argument gefunden, warum man es einstellen sollte NULL, um Maskierungsfehler zu vermeiden. stackoverflow.com/questions/1025589/… In beiden Fällen scheinen einige Fehler ausgeblendet zu sein.
Georg Schölly
1
Beachten
Secure
3
@ Chris, nein, der beste Ansatz ist die Codestruktur. Wirf keine zufälligen Mallocs und befreie deine gesamte Codebasis, sondern halte verwandte Dinge zusammen. Das "Modul", das eine Ressource (Speicher, Datei, ...) zuweist, ist für die Freigabe verantwortlich und muss dafür eine Funktion bereitstellen, die auch die Zeiger berücksichtigt. Für eine bestimmte Ressource haben Sie dann genau einen Ort, an dem sie zugewiesen ist, und einen Ort, an dem sie freigegeben wird, beide nahe beieinander.
Sichern Sie sich den
4
@ Chris Lutz: Hogwash. Wenn Sie Code schreiben, der denselben Zeiger zweimal freigibt, weist Ihr Programm einen logischen Fehler auf. Das Maskieren dieses logischen Fehlers, indem er nicht zum Absturz gebracht wird, bedeutet nicht, dass das Programm korrekt ist: Es macht immer noch etwas Unsinniges. Es gibt kein Szenario, in dem das Schreiben eines Double Free gerechtfertigt ist.
DrPizza
4

Ich finde, dass dies wenig hilfreich ist, da es meiner Erfahrung nach fast immer so ist, wenn Leute auf eine freigegebene Speicherzuordnung zugreifen, weil sie irgendwo einen anderen Zeiger darauf haben. Und dann widerspricht es einem anderen persönlichen Codierungsstandard, nämlich "Vermeiden Sie nutzlose Unordnung". Deshalb mache ich das nicht, da ich denke, dass es selten hilft und den Code etwas weniger lesbar macht.

Allerdings - ich werde die Variable nicht auf null setzen, wenn der Zeiger nicht erneut verwendet werden soll, aber oft gibt mir das übergeordnete Design einen Grund, sie trotzdem auf null zu setzen. Wenn der Zeiger beispielsweise Mitglied einer Klasse ist und ich gelöscht habe, worauf er verweist, lautet der "Vertrag" der Klasse, wenn Sie möchten, dass dieses Mitglied jederzeit auf etwas Gültiges verweist, sodass es auf null gesetzt werden muss aus diesem Grund. Eine kleine Unterscheidung, aber ich denke eine wichtige.

In c ++ ist es wichtig, immer darüber nachzudenken, wem es gehört diese Daten , wenn Sie einige Speicher zuweisen (es sei denn , Sie intelligente Zeiger verwenden , aber selbst dann ist einige Gedanken erforderlich). Und dieser Prozess führt dazu, dass Zeiger im Allgemeinen Mitglied einer Klasse sind und Sie möchten, dass sich eine Klasse jederzeit in einem gültigen Zustand befindet. Der einfachste Weg, dies zu tun, besteht darin, die Elementvariable auf NULL zu setzen, um anzuzeigen, dass sie zeigt zu nichts jetzt.

Ein allgemeines Muster besteht darin, alle Elementzeiger im Konstruktor auf NULL zu setzen und den Destruktoraufruf für alle Zeiger auf Daten löschen zu lassen, von denen Ihr Entwurf sagt, dass sie der Klasse gehören . In diesem Fall müssen Sie den Zeiger natürlich auf NULL setzen, wenn Sie etwas löschen, um anzuzeigen, dass Sie zuvor keine Daten besitzen.

Zusammenfassend lässt sich sagen, dass ich den Zeiger nach dem Löschen häufig auf NULL setze, aber dies ist Teil eines größeren Entwurfs und der Überlegungen, wem die Daten gehören, anstatt blind einer Codierungsstandardregel zu folgen. Ich würde dies in Ihrem Beispiel nicht tun, da ich denke, dass dies keinen Vorteil hat und es "Unordnung" hinzufügt, die meiner Erfahrung nach genauso für Fehler und schlechten Code verantwortlich ist wie diese Art von Dingen.

jcoder
quelle
4

Kürzlich bin ich auf dieselbe Frage gestoßen, nachdem ich nach der Antwort gesucht habe. Ich bin zu diesem Schluss gekommen:

Es ist eine bewährte Methode, und Sie müssen diese befolgen, um sie auf allen (eingebetteten) Systemen portierbar zu machen.

free()ist eine Bibliotheksfunktion, die sich ändert, wenn die Plattform geändert wird. Sie sollten also nicht erwarten, dass dieser Zeiger nach dem Übergeben des Zeigers auf diese Funktion und nach dem Freigeben des Speichers auf NULL gesetzt wird. Dies ist möglicherweise bei einigen für die Plattform implementierten Bibliotheken nicht der Fall.

also immer gehen für

free(ptr);
ptr = NULL;
Jalindar
quelle
3

Diese Regel ist nützlich, wenn Sie versuchen, die folgenden Szenarien zu vermeiden:

1) Sie haben eine sehr lange Funktion mit komplizierter Logik und Speicherverwaltung und möchten den Zeiger nicht versehentlich später in der Funktion wieder auf gelöschten Speicher verwenden.

2) Der Zeiger ist eine Mitgliedsvariable einer Klasse mit einem recht komplexen Verhalten, und Sie möchten den Zeiger nicht versehentlich für gelöschten Speicher in anderen Funktionen wiederverwenden.

In Ihrem Szenario macht es nicht viel Sinn, aber wenn die Funktion länger wird, könnte es wichtig sein.

Sie können argumentieren, dass das Setzen auf NULL später tatsächlich Logikfehler maskieren kann, oder wenn Sie davon ausgehen, dass es gültig ist, stürzen Sie immer noch auf NULL ab, sodass dies keine Rolle spielt.

Im Allgemeinen würde ich Ihnen raten, es auf NULL zu setzen, wenn Sie denken, dass es eine gute Idee ist, und sich nicht darum zu kümmern, wenn Sie denken, dass es sich nicht lohnt. Konzentrieren Sie sich stattdessen darauf, kurze Funktionen und gut gestaltete Klassen zu schreiben.

i_am_jorf
quelle
2

Eine gute Methode zur Verwendung von Zeigern besteht darin, immer zu überprüfen, ob es sich um einen gültigen Zeiger handelt oder nicht. Etwas wie:


if(ptr)
   ptr->CallSomeMethod();

Das explizite Markieren des Zeigers als NULL nach dem Freigeben ermöglicht diese Art der Verwendung in C / C ++.

Aamir
quelle
5
In vielen Fällen, in denen ein NULL-Zeiger keinen Sinn ergibt, ist es vorzuziehen, stattdessen eine Zusicherung zu schreiben.
Erich Kitzmüller
2

Dies könnte eher ein Argument sein, um alle Zeiger auf NULL zu initialisieren, aber so etwas kann ein sehr hinterhältiger Fehler sein:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

plandet an derselben Stelle auf dem Stapel wie der erstere nPtr, sodass er möglicherweise noch einen scheinbar gültigen Zeiger enthält. Das Zuweisen zu *pkann alle Arten von nicht verwandten Dingen überschreiben und zu hässlichen Fehlern führen. Insbesondere, wenn der Compiler lokale Variablen im Debug-Modus mit Null initialisiert, dies jedoch nicht tut, sobald die Optimierungen aktiviert sind. Die Debug-Builds zeigen also keine Anzeichen des Fehlers, während die Release-Builds zufällig explodieren ...

etw
quelle
2

Das Setzen des Zeigers, der gerade freigegeben wurde, auf NULL ist nicht obligatorisch, aber eine gute Vorgehensweise. Auf diese Weise können Sie vermeiden, 1) einen freigegebenen spitzen 2) zu verwenden

pierrotlefou
quelle
2

Einstellungen Ein Zeiger auf NULL dient zum Schutz vor so genannten Double-Free - eine Situation, in der free () für dieselbe Adresse mehrmals aufgerufen wird, ohne den Block an dieser Adresse neu zuzuweisen.

Double-Free führt zu undefiniertem Verhalten - normalerweise Heap-Beschädigung oder sofortiger Absturz des Programms. Das Aufrufen von free () für einen NULL-Zeiger führt zu nichts und ist daher garantiert sicher.

Die beste Vorgehensweise, es sei denn, Sie sind sich jetzt sicher, dass der Zeiger den Gültigkeitsbereich sofort oder sehr bald nach free () verlässt, besteht darin, diesen Zeiger auf NULL zu setzen. Selbst wenn free () erneut aufgerufen wird, wird er jetzt für einen NULL-Zeiger und ein undefiniertes Verhalten aufgerufen wird umgangen.

scharfer Zahn
quelle
2

Die Idee ist, dass Sie, wenn Sie versuchen, den nicht mehr gültigen Zeiger nach dem Freigeben zu dereferenzieren, eher hart (segfault) als still und geheimnisvoll versagen möchten.

Aber sei vorsichtig. Nicht alle Systeme verursachen einen Segfault, wenn Sie NULL dereferenzieren. Unter (zumindest einigen Versionen von) AIX ist * (int *) 0 == 0 und Solaris optional mit dieser AIX- "Funktion" kompatibel.

Jaap Weel
quelle
2

Zur ursprünglichen Frage: Das Setzen des Zeigers auf NULL direkt nach dem Freigeben des Inhalts ist reine Zeitverschwendung, vorausgesetzt, der Code erfüllt alle Anforderungen, ist vollständig debuggt und wird nie wieder geändert. Auf der anderen Seite kann es sehr nützlich sein, einen freigegebenen Zeiger defensiv NULL zu machen, wenn jemand gedankenlos einen neuen Codeblock unter free () hinzufügt, wenn das Design des Originalmoduls nicht korrekt ist und wenn dies der Fall ist -kompiliert-aber-tut-nicht-was-ich-will Fehler.

In jedem System gibt es ein unerreichbares Ziel, es am einfachsten zu machen, das Richtige zu finden, und die irreduziblen Kosten für ungenaue Messungen. In C werden uns eine Reihe sehr scharfer, sehr starker Werkzeuge angeboten, die in den Händen eines Facharbeiters viele Dinge erzeugen und bei unsachgemäßer Handhabung alle möglichen metaphorischen Verletzungen verursachen können. Einige sind schwer zu verstehen oder richtig zu verwenden. Und Menschen, die von Natur aus risikoscheu sind, tun irrationale Dinge wie das Überprüfen eines Zeigers auf den NULL-Wert, bevor sie damit frei anrufen ...

Das Messproblem besteht darin, dass je komplexer der Fall ist, desto wahrscheinlicher ist es, dass Sie eine mehrdeutige Messung erhalten, wenn Sie versuchen, Gut von weniger Gut zu trennen. Wenn das Ziel darin besteht, nur gute Praktiken beizubehalten, werden einige mehrdeutige mit den eigentlich nicht guten verworfen. WENN Ihr Ziel darin besteht, das Nicht-Gute zu beseitigen, können die Unklarheiten beim Guten bleiben. Die beiden Ziele, nur gut zu bleiben oder eindeutig schlecht zu eliminieren, scheinen diametral entgegengesetzt zu sein, aber es gibt normalerweise eine dritte Gruppe, die weder die eine noch die andere ist, einige von beiden.

Bevor Sie sich an die Qualitätsabteilung wenden, durchsuchen Sie die Fehlerdatenbank, um festzustellen, wie oft, wenn überhaupt, ungültige Zeigerwerte Probleme verursachten, die notiert werden mussten. Wenn Sie einen echten Unterschied machen möchten, identifizieren Sie das häufigste Problem in Ihrem Produktionscode und schlagen Sie drei Möglichkeiten vor, um dies zu verhindern

Rechnung IV
quelle
Gute Antwort. Ich möchte eine Sache hinzufügen. Das Überprüfen der Fehlerdatenbank ist aus verschiedenen Gründen sinnvoll. Beachten Sie jedoch, dass es im Zusammenhang mit der ursprünglichen Frage schwierig ist zu wissen, wie viele ungültige Zeigerprobleme verhindert oder zumindest so früh erkannt wurden, dass sie nicht in die Fehlerdatenbank aufgenommen wurden. Der Fehlerverlauf bietet bessere Beweise für das Hinzufügen von Codierungsregeln.
Jimhark
2

Es gibt zwei Gründe:

Vermeiden Sie Abstürze beim Double-Freeing

Geschrieben von RageZ in einer doppelten Frage .

Der häufigste Fehler in c ist das Double Free. Grundsätzlich machst du so etwas

free(foobar);
/* lot of code */
free(foobar);

und es endet ziemlich schlecht, das Betriebssystem versucht, etwas bereits freigegebenen Speicher freizugeben und im Allgemeinen segfault es. Die bewährte Methode ist also, auf zu setzen NULL, damit Sie testen und prüfen können, ob Sie diesen Speicher wirklich freigeben müssen

if(foobar != NULL){
  free(foobar);
}

free(NULL) Beachten Sie auch, dass dies nichts bewirkt, sodass Sie die if-Anweisung nicht schreiben müssen. Ich bin nicht wirklich ein OS-Guru, aber ich bin ziemlich hübsch, selbst jetzt würden die meisten OSes doppelt doppelt abstürzen.

Dies ist auch ein Hauptgrund, warum alle Sprachen mit Garbage Collection (Java, Dotnet) so stolz darauf waren, dieses Problem nicht zu haben und auch nicht die gesamte Speicherverwaltung dem Entwickler überlassen zu müssen.

Vermeiden Sie die Verwendung bereits freigegebener Zeiger

Geschrieben von Martin v. Löwis in einer anderen Antwort .

Das Setzen nicht verwendeter Zeiger auf NULL ist ein defensiver Stil, der vor baumelnden Zeigerfehlern schützt. Wenn auf einen baumelnden Zeiger zugegriffen wird, nachdem er freigegeben wurde, können Sie zufälligen Speicher lesen oder überschreiben. Wenn auf einen Nullzeiger zugegriffen wird, kommt es auf den meisten Systemen sofort zu einem Absturz, der Sie sofort über den Fehler informiert.

Bei lokalen Variablen kann es etwas sinnlos sein, wenn "offensichtlich" ist, dass nach dem Freigeben nicht mehr auf den Zeiger zugegriffen wird. Daher ist dieser Stil besser für Mitgliedsdaten und globale Variablen geeignet. Selbst für lokale Variablen kann es ein guter Ansatz sein, wenn die Funktion nach der Freigabe des Speichers fortgesetzt wird.

Um den Stil zu vervollständigen, sollten Sie Zeiger auch auf NULL initialisieren, bevor ihnen ein echter Zeigerwert zugewiesen wird.

Georg Schölly
quelle
1

Da Sie ein Qualitätssicherungsteam haben, möchte ich noch einen kleinen Punkt zur Qualitätssicherung hinzufügen. Einige automatisierte QS-Tools für C kennzeichnen Zuweisungen an freigegebene Zeiger als "nutzlose Zuweisung an ptr". Zum Beispiel sagt PC-Lint / FlexeLint von Gimpel Software tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

Es gibt Möglichkeiten, Nachrichten selektiv zu unterdrücken, sodass Sie beide QS-Anforderungen erfüllen können, falls Ihr Team dies entscheidet.

Jens
quelle
1

Es ist immer ratsam, eine Zeigervariable mit NULL zu deklarieren, z.

int *ptr = NULL;

Angenommen , ptr zeigt auf die Speicheradresse 0x1000 . Nach der Verwendung free(ptr)ist es immer ratsam, die Zeigervariable durch erneutes Deklarieren auf NULL zu annullieren . z.B:

free(ptr);
ptr = NULL;

Wenn die Zeigervariable nicht erneut zu NULL deklariert wird, zeigt sie weiterhin auf dieselbe Adresse ( 0x1000 ). Diese Zeigervariable wird als baumelnder Zeiger bezeichnet . Wenn Sie eine andere Zeigervariable definieren (z. B. q ) und dem neuen Zeiger dynamisch eine Adresse zuweisen, besteht die Möglichkeit, dass dieselbe Adresse ( 0x1000 ) von einer neuen Zeigervariablen übernommen wird. Wenn Sie denselben Zeiger ( ptr ) verwenden und den Wert an der Adresse aktualisieren, auf die derselbe Zeiger ( ptr ) zeigt, schreibt das Programm am Ende einen Wert an die Stelle, auf die q zeigt (seit p und q sind auf dieselbe Adresse zeigen (0x1000) )).

z.B

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.
Pankaj Kumar Thapa
quelle
1

Lange Rede, kurzer Sinn: Sie möchten nicht versehentlich (versehentlich) auf die Adresse zugreifen, die Sie freigegeben haben. Wenn Sie die Adresse freigeben, können Sie zulassen, dass diese Adresse im Heap einer anderen Anwendung zugewiesen wird.

Wenn Sie den Zeiger jedoch nicht auf NULL setzen und versehentlich versuchen, den Zeiger zu de-referenzieren, oder den Wert dieser Adresse ändern. SIE KÖNNEN ES NOCH TUN. ABER NICHT ETWAS, WAS SIE LOGISCH WOLLEN.

Warum kann ich immer noch auf den Speicherplatz zugreifen, den ich freigegeben habe? Denn: Möglicherweise haben Sie den Speicher freigegeben, aber die Zeigervariable enthielt noch Informationen zur Heapspeicheradresse. Als Verteidigungsstrategie setzen Sie sie bitte auf NULL.

Ehsan
quelle