Was passiert genau, wenn ich einen Point mit NULL initialisiere?
-
Guten Morgen
Ich habe gerade etwas herumgespielt und mir ist dabei etwas aufgefallen.
Funktioniert:
int main() { int *a = NULL; delete a; }
Undefiniertes Verhalten:
int main() { int *a; delete a; }
Könnte mir vielleicht jemand beschreiben was genau passiert, wenn ich einen Pointer mit NULL initialisiere? Wird dann Speicher angefordert in der Grösse des Typs und einfach nicht mit einem Wert initialisiert?
Wie arbeitet delete genau? Soweit ich weiss, gibt es einfach eine gewisse Menge (abhängig vom Typ des Pointers) an Speicher frei. Die Adresse des freizugebenden Speichers hat dann ja der Pointer gespeichert. Stimm das so?
-
Ein mit NULL (0) initialisierter Pointer zeigt nirgendwo hin. Ein delete auf so einen Zeiger funktioniert, macht aber nichts.
Ein nicht initialisierter Zeiger zeigt irgendwo hin, wo mit sehr großer Wahrscheinlichkeit kein Speicher zum Freigeben ist. Deshalb funktioniert delete damit nicht, bzw. erzeugt undefiniertes Verhalten.
Wenn man mit delete freigeben will, muß der Zeiger auf vorher mit new angeforderten Speicher zeigen, oder halt 0 sein ...
-
Eine Zeigervariable ist genau das gleiche, wie jede andere Variable. Wenn Du sie nicht initialiserst, steht da irgendwas zufälliges drin. Der Standard definiert ein Verhalten für
delete
auf Nullzeiger (kein Effekt) und fürdelete
auf korrekt initialisierten, dynamischen Speicher (Speicher freigeben). Wenn ein zufälliger Wert in in einer Zeigervariable steht, dann zeigt sie auf nicht korrekt initialiserten Speicher. Das Verhalten hierfür ist undefiniert. In Deinem Fall kracht es.
-
Achso, alles klar.
Ist es in dem Fall besser, wenn man einen Pointer, den man nicht gleich mit new initialiseren kann, zuerst mit NULL initialisiert?
Ich weiss, das braucht man wohl meistens nicht. Ich mag rohe Pointer onehin nicht ^^ Aber angenommen, man bräuchte es einmal
-
Ich finde das gehört dann zur Pflicht.
-
icarus2 schrieb:
Ist es in dem Fall besser, wenn man einen Pointer, den man nicht gleich mit new initialiseren kann, zuerst mit NULL initialisiert?
Das kommt auf den konkreten Fall an. In der Regel solltest du, wenn du schon mit Zeigern arbeitest, zu jedem Zeitpunkt des Programmablaufs wissen, worauf deine Zeiger zeigen. Eine unnötige Nullinitialisierung verdeckt dann nur Fehler eines falschen deletes, der Fehler ist aber immer noch da.
-
Alles klar. Vielen Danke für eure Antworten
-
SeppJ schrieb:
In der Regel solltest du, wenn du schon mit Zeigern arbeitest, zu jedem Zeitpunkt des Programmablaufs wissen, worauf deine Zeiger zeigen.
Ja tue ich das denn nicht damit, indem ich die Zeiger definiert auf NULL initialisiere?
[/quote]
Eine unnötige Nullinitialisierung verdeckt dann nur Fehler eines falschen deletes, der Fehler ist aber immer noch da.[/quote]
Verstehe ich nicht, wie kann ein delete auf einen NULL-Zeiger falsch sein? Lieber ein delete zu viel, als eins vergessen zu haben. Ich hab schon öfter Code gesehen, wo new und delete nicht zusammen aufgerufen wurden. Trotzdem sollte man nicht vergessen ein evtl. aufgerufenes new wieder frei zu geben. Dafür verwende ich ja NULL, dass ich feststellen kann, ob ein Zeiger gesetzt ist oder nicht.
-
Paul Müller schrieb:
SeppJ schrieb:
In der Regel solltest du, wenn du schon mit Zeigern arbeitest, zu jedem Zeitpunkt des Programmablaufs wissen, worauf deine Zeiger zeigen.
Ja tue ich das denn nicht damit, indem ich die Zeiger definiert auf NULL initialisiere?
Wie gesagt, kommt drauf an: Es kann Sinn machen, Zeiger auf Null zeigen zu lassen. Wenn du aber alle Zeiger erstmal auf Null setzt, dann ein Objekt löschen willst, dass du gar nicht mit new angelegt hast, dann passiert dabei nichts. Aber es war dennoch ein Fehler im Programm, denn du dachtest ja, es wäre ein Objekt da zum Löschen.
Eine unnötige Nullinitialisierung verdeckt dann nur Fehler eines falschen deletes, der Fehler ist aber immer noch da.
Verstehe ich nicht, wie kann ein delete auf einen NULL-Zeiger falsch sein? Lieber ein delete zu viel, als eins vergessen zu haben. Ich hab schon öfter Code gesehen, wo new und delete nicht zusammen aufgerufen wurden. Trotzdem sollte man nicht vergessen ein evtl. aufgerufenes new wieder frei zu geben. Dafür verwende ich ja NULL, dass ich feststellen kann, ob ein Zeiger gesetzt ist oder nicht.
Eban nicht! Ein delete auf gut Glück heißt, dass du keine Ahnung hast wie dein Programm abläuft. Computerprogramme sind keine zufällige Abfolge von Codefetzen die man so lange umstellt bis etwas funktioniert. Code ist reine Logik. Du musst genau wissen was wann warum passiert.
-
[quote="SeppJ"]Wenn du aber alle Zeiger erstmal auf Null setzt, dann ein Objekt löschen willst, dass du gar nicht mit new angelegt hast, dann passiert dabei nichts. Aber es war dennoch ein Fehler im Programm, denn du dachtest ja, es wäre ein Objekt da zum Löschen.
Das kann ich nicht nachvollziehen, hast du mal ein Beispiel? Ein Zeiger den ich nicht mit new einen Wert zugewiesen habe zu deleten geht in jedem Fall schief. Ich weiß es jetzt nicht, aber je nach RT wird sogar dein Programm abstürzen. Deswegen ist sowas in jedem Fall zu vermeiden.
[quote="SeppJ"]
Eban nicht! Ein delete auf gut Glück heißt, dass du keine Ahnung hast wie dein Programm abläuft.Nein, ein einfaches Beispiel. Du hast eine Methode, die in einen Puffer schreibt. Der Puffer ist aber nicht immer ausreichend groß, zum Beispiel am Anfang gar nicht existent. Eine initiale Größe zu geben ist auch unsinnig, wenn du Pech hast musst du den Puffer gleich wieder vergrößern. Anstatt nun aber bei jedem Durchlauf zu checken ob es überhaupt einen Puffer gibt der vorm vergrößern gelöscht werden muss, nutze ich lieber die Eigenschaft von delete.
SeppJ schrieb:
Computerprogramme sind keine zufällige Abfolge von Codefetzen die man so lange umstellt bis etwas funktioniert. Code ist reine Logik. Du musst genau wissen was wann warum passiert.
Und das kann man am besten mit einer definierten Logik. Es nützt dir überhaupt nichts, dass dein Programm aufgrund von zufälligen Werten mal geht und mal nicht.
-
Paul Müller schrieb:
Und das kann man am besten mit einer definierten Logik. Es nützt dir überhaupt nichts, dass dein Programm aufgrund von zufälligen Werten mal geht und mal nicht.
Das Problem ist ja nicht das delete selber. Das wäre ja OK. Das Problem ist das drumherum. Du rufst ja nicht einfach nur delete auf, sondern da steckt ja eine Menge mehr Logik drin. Und wenn das delete falsch ist, dann wird auch der restliche Code mit einer hohen wahrscheinlichkeit Fehlerhaft sein. Und das ist die Gefahr.
Ein delete auf einen uninitialisierten Zeiger fällt dagegen bei den Tests auf. Speziell im Debug Modus, wo unitialisiert bedeutet, dass ein bestimmter Wert drinnen steht und auf den springt der Debugger an.
-
Shade Of Mine schrieb:
Ein delete auf einen uninitialisierten Zeiger fällt dagegen bei den Tests auf. Speziell im Debug Modus, wo unitialisiert bedeutet, dass ein bestimmter Wert drinnen steht und auf den springt der Debugger an.
Diese Aussage sehe ich als gefährlich an, da ich es durchaus von einigen Debuggern kenne das man uninitialisierte Zeiger im Debugmodus eben nicht bemerkt, da sie vom Debugger mit 0 initialisiert wurden, im Release aber ein Zufallswert in diesen steht.
Zumindest habe ich uninitialisierte Zeiger bislang eigentlich ausschließlich im Releasemodus nach längerer Suche gefunden (einer der Gründe warum ich immer versuche Leute auf die Verwendung von Initialisierungslisten und allgemein möglichst direkten Initialisierungen von Variablen zu drängen).
-
asc schrieb:
Diese Aussage sehe ich als gefährlich an, da ich es durchaus von einigen Debuggern kenne das man uninitialisierte Zeiger im Debugmodus eben nicht bemerkt, da sie vom Debugger mit 0 initialisiert wurden, im Release aber ein Zufallswert in diesen steht.
Und da sieht man wieder, warum es falsch ist, Zeiger grundsätzlich mit 0 zu initialisieren.
Zeiger setzt man auf 0, wenn man 0 meint. Nicht dann wenn man keine Ahnung hat, was man sonst machen soll.
-
Warum wird ein delete von einem Zeiger, der auf 0 zeigt eigentlich abgefangen?
Das macht doch bei jedem delete intern eine zusätzliche if-Abfrage für
einen Spezialfall...
-
Idealerweise vermeidet man es, direkt auf Zeigern zu arbeiten und benutzt passende RAII-Wrapper...
-
Ja gut, das ganze ist zu dem Zeitpunkt aber doch definiert, nur halt als bad pointer. Soweit mag das ganze also noch zu funktionieren und ist auch nachvollziehbar. Nur was machst du, wenn der Zeiger schon mal deleted wurde? Ich setze ihn einfach NULL und weiß was los ist. Ich hab es auch teilweise mit Code zu tun, wo ich an Stelle B vergessen habe, ob ich den Speicher an Stelle A schon frei gegeben habe. Und wenn du Pech hast, gabs schon mal wieder ein new zwischendurch, welches genau diesen Zeiger zurück gegeben hat. Dann ist dein delete erfolgreich, aber trotzdem funktioniert nichts. Ich würde sogar soweit gehen und delete einfach den Zeiger NULL setzen lassen. Aber das wird wahrscheinlich nicht den Weg in den Standard finden. Diese eine Anweisung tut keinem weh, im Verhältnis zum restlichen delete, aber würde soviel bringen. Die Smartpointer machen es ja soweit ich mich entsinne genauso. Wobei es dort obligatorisch ist.
-
Im Prinzip ist es doch praktisch, dass delete auf NULL prüft, somit kann man seinen Code lesbarer gestalten.
-
@Paul Müller: Das klingt alles so, als würdest du so mit Pointern programmieren, dass man darauf tatsächlich aufpassen müsste, d.h. einfach wild drauflos. Idealerweise programmiert man so, dass solche Probleme gar nicht erst auftreten können. Die Sprache kommt einem da sehr entgegen. Guck dir mal an, was RAII bedeutet.
allgemein: delete prüft ja ohnehin irgendwie, ob die Freigabe gültig ist. Da ist es doch kein Problem, die 0 gesondert zu behandeln, ohne dass es merkliche Laufzeitverluste gibt.
-
SeppJ schrieb:
asc schrieb:
Diese Aussage sehe ich als gefährlich an, da ich es durchaus von einigen Debuggern kenne das man uninitialisierte Zeiger im Debugmodus eben nicht bemerkt, da sie vom Debugger mit 0 initialisiert wurden, im Release aber ein Zufallswert in diesen steht.
Und da sieht man wieder, warum es falsch ist, Zeiger grundsätzlich mit 0 zu initialisieren.
Ein Zufallswert ist aber genauso unsinnig.
Ich initialisiere Zeiger (wenn ich normale verwende) grundsätzlich mit 0, wenn sie noch keinen Wert zugewiesen haben, ansonsten mit einer gültigen Speicheradresse. Ein anderer Initialisierungswert macht imho nur dann Sinn, wenn es einen eindeutigen Wert für "Uninitialisiert" gäbe.
-
asc schrieb:
Ein Zufallswert ist aber genauso unsinnig.
Ich initialisiere Zeiger (wenn ich normale verwende) grundsätzlich mit 0, wenn sie noch keinen Wert zugewiesen haben, ansonsten mit einer gültigen Speicheradresse. Ein anderer Initialisierungswert macht imho nur dann Sinn, wenn es einen eindeutigen Wert für "Uninitialisiert" gäbe.
Warum das? Andere Variablen, welche kein Zeigertyp sind, initialisiert man ja auch nicht zwangsläufig sofort mit einem sinnvollen Wert.
Ohne Zweisung bzw. new macht ein Zeiger wenig Sinn. Und danach zeigt er entweder auf etwas sinnvolles, oder auf 0 (oder es wird eine Exception geschmissen, wenn man es sagt). Und dann sollte man eh prüfen, ob alles gut gegangen ist.