Was passiert genau, wenn ich einen Point mit NULL initialisiere?



  • 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).


  • Mod

    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.


  • Mod

    @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.



  • Tachyon schrieb:

    Warum das? Andere Variablen, welche kein Zeigertyp sind, initialisiert man ja auch nicht zwangsläufig sofort mit einem sinnvollen Wert.

    Ich versuche Variablen immer erst zu deklarieren, wenn ich sie initialisieren kann, und in der Initialisierungsliste führe ich schon alleine um keine Variable zufällig zu übersehen auch alle auf. In sofern sehe ich das anders.

    Tachyon schrieb:

    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.

    Dieser Ansatz kann aber sehr schnell nach hinten gehen, vor allem wenn man Programmierer im Team hat, die Variablen ganz weit entfernt von der Verwendung deklarieren. Ich habe lieber ein sauber definiertes Verhalten (selbst eine AccessViolation mit Zugriff auf Adresse 0 halte ich für sinnvoller als Zufallswerte die weiß was bedeuten können, und die im Laufenden Betrieb schlecht reproduzierbare Fehler nach sich ziehen).

    Ich riskiere lieber einen zu 100% reproduzierbaren Absturz als einen, der verdammt schwierig zu finden ist, und ggf. auch von der QS übersehen wird [der reproduzierbare Absturz wird weit leichter gefunden].



  • SeppJ schrieb:

    @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.

    Das hat mit meinen Programmierkünsten erstmal wenig zu tun. Da gibt es halt Code, den hat irgendjemand anderes mal geschrieben und da musst du jetzt was ran/rein basteln. Natürlich ist da nicht die Lebenszeit jedes einzelnen Pointers dokumentiert. Aber dafür gibt es umso mehr Pointer und dirty Tricks, die man mit Pointern machen kann.

    SeppJ schrieb:

    Idealerweise programmiert man so, dass solche Probleme gar nicht erst auftreten können.

    Das ist meist schwierig, wenn es um zeitkritische Aufgaben geht.

    SeppJ schrieb:

    allgemein: delete prüft ja ohnehin irgendwie, ob die Freigabe gültig ist.

    Im einfachsten Fall würdest du dann eine Exception bekommen, diese müsste du aber wieder gesondert behandeln. Und wie schon angemerkt, was heißt gültig, ein Speicherbereich kann auch wieder vergeben werden.

    SeppJ schrieb:

    Da ist es doch kein Problem, die 0 gesondert zu behandeln, ohne dass es merkliche Laufzeitverluste gibt.

    Es geht ja auch mehr darum definieren zu können, ob ein Zeiger gültig ist oder nicht. Von new und delete soll man ja auch die Finger lassen soweit es möglich ist.



  • Paul Müller schrieb:

    ...Von new und delete soll man ja auch die Finger lassen soweit es möglich ist...

    Das würde ich so nicht stehen lassen, auch wenn man sich fragen sollte ob man sich selbst um die Speicherverwaltung kümmern muss. Stackvariablen sind Heapvariablen zwar häufig vorzuziehen, nur ist der Stack zumeist begrenzt, und manches lässt sich auf dem Heap besser regeln.



  • Paul Müller schrieb:

    ...
    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.

    Bjarne Stroustrup schrieb:

    C++ explicitly allows an implementation of delete to zero out an lvalue operand, and I had hoped that implementations would do that, but that idea doesn't seem to have become popular with implementers.

    If you consider zeroing out pointers important, consider using a destroy function:

    template<class T> inline void destroy(T*& p) { delete p; p = 0; }

    Man beachte aber auch http://www2.research.att.com/~bs/bs_faq2.html#delete-zero



  • 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.

    Ist ne ziemliche schwäche des Compilers dann.
    Der VC++ setzt zB uninitialisierte Zeiger auf 0xCDCDCDCD

    Und jeder Zugriff auf diese Adresse ist sofort ein breakpoint.



  • asc schrieb:

    Das würde ich so nicht stehen lassen, auch wenn man sich fragen sollte ob man sich selbst um die Speicherverwaltung kümmern muss.

    Darauf wollte ich jetzt gar nicht hinaus. Mir ging es um die Zeit, die ein new/delete verbrät. Deswegen sollten man damit lieber sparsam umgehen. Objekte verschlimmern diese ganze Angelegenheit auch nur.
    Das hängt aber auch stark von der Zielplattform ab. Auf einem normalen Desktop-Computer braucht man sich in der Regel keine Gedanken darum machen.


  • Mod

    Paul Müller schrieb:

    ...gefährliches Halbwissen...

    Tut mir leid, aber fast alles was du in deinen letzten beiden Beiträgen geschrieben hast ist Quatsch.



  • @SeppJ
    Was den genau? Ich kann im letzten Beitrag von mir keinen Unsinn entdecken. Maximal eine Pauschalisierung bzgl. ob man auf Desktops ressourcensparend programmieren sollten. Wenn ich dich aber richtig verstehe, würdest du die Aussage sogar ausweiten, anstatt einschränken wollen?


  • Mod

    Paul Müller schrieb:

    @SeppJ
    Was den genau? Ich kann im letzten Beitrag von mir keinen Unsinn entdecken. Maximal eine Pauschalisierung bzgl. ob man auf Desktops ressourcensparend programmieren sollten. Wenn ich dich aber richtig verstehe, würdest du die Aussage sogar ausweiten, anstatt einschränken wollen?

    Ok, hier eine detaillierte Analyse:

    Mir ging es um die Zeit, die ein new/delete verbrät. Deswegen sollten man damit lieber sparsam umgehen.

    Und was soll man stattdessen nehmen? Entweder braucht man Heapobjekte oder eben nicht. Grundlos new/delete benutzen meistens nur Leute die von Java kommen, weil sie es nicht anders kennen.

    Objekte verschlimmern diese ganze Angelegenheit auch nur.

    Unsinn. Abstraktion kostet keine Laufzeit.

    SeppJ schrieb:

    Idealerweise programmiert man so, dass solche Probleme gar nicht erst auftreten können.

    Das ist meist schwierig, wenn es um zeitkritische Aufgaben geht.

    Unsinn. Abstraktion kostet keine Laufzeit.

    SeppJ schrieb:

    allgemein: delete prüft ja ohnehin irgendwie, ob die Freigabe gültig ist.

    Im einfachsten Fall würdest du dann eine Exception bekommen, diese müsste du aber wieder gesondert behandeln.

    Was willst du mir damit sagen? Ich schildere nur den Sachverhalt. Es ging darum, ob das delete dadurch langsamer wird, weil es die 0 gesondert behandelt. Das wird es nicht wesentlich, weil beim delete sowieso irgendwelche Prüfungen durchgeführt werden.

    Und wie schon angemerkt, was heißt gültig, ein Speicherbereich kann auch wieder vergeben werden.

    Gültig heißt, dass es vorher ein passendes new gab. Speicher wird nicht wieder vergeben, bevor er nicht freigegeben wurde. Wenn du über mehrere Pointer auf den gleichen Speicherbereich zugreifst (so dass er zwischendurch freigegeben und wieder zugewiesen werden kann, ohne dass ein anderer Pointer es merkt), sind wir wieder im Bereich unsauberer Programmierung.

    Von new und delete soll man ja auch die Finger lassen soweit es möglich ist.

    Da haste Recht. Aber ich glaube die Gründe sind dir nicht so ganz klar.



  • SeppJ schrieb:

    Und was soll man stattdessen nehmen? Entweder braucht man Heapobjekte oder eben nicht.

    Man kann die Objekte auch einfach recyceln. Das man irgendwann mal ein Objekt erstellen muss steht dabei nicht zur Debatte.

    SeppJ schrieb:

    Unsinn. Abstraktion kostet keine Laufzeit.

    Du willst mir nicht ernsthaft erzählen, dass ein ctor/dtor Aufruf genauso viel Laufzeit benötigt wie kein Aufruf?

    SeppJ schrieb:

    Unsinn. Abstraktion kostet keine Laufzeit.

    Hier das gleiche. Du meinst nicht ernsthaft etwas zu machen geht genauso schnell, wie etwas nicht zu machen?

    SeppJ schrieb:

    Was willst du mir damit sagen? Ich schildere nur den Sachverhalt. Es ging darum, ob das delete dadurch langsamer wird, weil es die 0 gesondert behandelt. Das wird es nicht wesentlich, weil beim delete sowieso irgendwelche Prüfungen durchgeführt werden.

    Meinetwegen werden Prüfungen durchgeführt. Diese belaufen sich aber auf NULL, der Trivialfall. Und ob der Zeiger mit new allokiert wurde. Andernfalls gibts eine Exception, die du fangen müsstest und irgendwas damit anstellen oder dein Programm kracht komplett weg.
    Die mühselige Exception kann ich mir sparen, indem ich einfach den Pointer NULL setze. Und das hab ich zwar schon erwähnt aber ein delete zuviel tut nicht weh, nur ein delete zu wenig. Und wenn es nicht weh tut, ist es mir auch egal, ob man es als unsauber ansehen kann. Der Link von manni66 zeigt zwar, dass das nicht immer funktioniert, aber das kannst du ja in deinem Design berücksichtigen.

    SeppJ schrieb:

    Gültig heißt, dass es vorher ein passendes new gab. Speicher wird nicht wieder vergeben, bevor er nicht freigegeben wurde.

    Das hat auch niemand bestritten.

    SeppJ schrieb:

    Wenn du über mehrere Pointer auf den gleichen Speicherbereich zugreifst (so dass er zwischendurch freigegeben und wieder zugewiesen werden kann, ohne dass ein anderer Pointer es merkt), sind wir wieder im Bereich unsauberer Programmierung.

    Siehe meine Anmerkung zu manni66 seinem Link.

    SeppJ schrieb:

    Da haste Recht. Aber ich glaube die Gründe sind dir nicht so ganz klar.

    Ich denke schon. Ich finde es nur etwas merkwürdig das du bestreiten willst, dass ein Funktionsaufruf Laufzeit kostet. Ich denke die Bedeutung von inline-Funktionen ist dir vertraut?


Anmelden zum Antworten