Was passiert genau, wenn ich einen Point mit NULL initialisiere?
-
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.
-
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 0xCDCDCDCDUnd 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.
-
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?
-
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?
-
Paul Müller schrieb:
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?
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Die mühselige Exception kann ich mir sparen, indem ich einfach den Pointer NULL setze.
Die Exception und alles andere kannst du dir gleich sparen, indem du vernünftig programmierst und nicht irgendwelche Zeiger löscht, von denen du nicht einmal weißt, wohin sie zeigen.
Und das hab ich zwar schon erwähnt aber ein delete zuviel tut nicht weh, nur ein delete zu wenig.
Wenn du ein delete zu viel machst, dann hat dein Programm einen Fehler. An anderer Stelle. Und du merkst es noch nicht einmal, wenn du immer blind alles nullst.
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?[/quote]Besser als dir offensichtlich.
-
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
SeppJ schrieb:
Die Exception und alles andere kannst du dir gleich sparen, indem du vernünftig programmierst und nicht irgendwelche Zeiger löscht,
Ich lösche nicht irgendwelche Zeiger. Das löschen ist auch wie schon mal angemerkt völlig uninteressant. Viel wichtiger ist es keine falschen Zeiger zu dereferenzieren. Und dass kann ich mit NULL auch viel besser machen, als wenn ich mir das Programm um die Ohren hauen lasse.
SeppJ schrieb:
von denen du nicht einmal weißt, wohin sie zeigen.
Was ja nur der Fall wäre, wenn ich sie nicht nullen würde. Siehe das Beispiel, wo der Zeiger oder besser die Adresse, schon wieder für eine Allokation verwendet wurde.
SeppJ schrieb:
Wenn du ein delete zu viel machst, dann hat dein Programm einen Fehler. An anderer Stelle. Und du merkst es noch nicht einmal, wenn du immer blind alles nullst.
Ich nulle nicht blind alles, sondern nur Zeiger die ich gelöscht habe. Somit merke ich dann in dem Fall schmerzlos, dass ich das Objekt zu früh gelöscht habe. Wenn ich einfach einen gültigen Zeiger NULL setze und damit die Verbindung verliere, um das Objekt zu löschen, dies wäre fatal, richtig.
-
Paul Müller schrieb:
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
Und der Anfänger läge damit falsch. Du auch. Seit wann ist Speicherfüllen ein Funktionsaufruf?
-
SeppJ schrieb:
Paul Müller schrieb:
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
Und der Anfänger läge damit falsch. Du auch. Seit wann ist Speicherfüllen ein Funktionsaufruf?
Also ihr sprecht da irgendwie über 2 verschiedene Sachen.
Initialisierung von Variablen kostet Zeit gegenüber dem Nichtinitialisieren. Ich glaube da sind wir uns einig.
Und ob der Konstruktor ein Funktionsaufruf ist oder nicht ist völlig plattformabhängig und wahrscheinlich auch davon was der Konstruktor im Endeffekt tut. Wenn es nur einen leeren Defaulkonstruktor gibt wird da wahrscheinlich nichts gemacht, sind aber mehrere verschiedene Konstruktoren vorhanden werden das sehr wahrscheinlich Funktionsaufrufe sein. Man müsste das halt mal überprüfen und ein wenig asm anschauen, aber ich kann mir das kaum anders vorstellen.
-
SeppJ hat vollkommen recht.
Denn wenn man im Ctor 3h lang initialisiert, so muss man das ohne Ctor ja auch tun, sonst wäre der Code nicht äquivalent. Wenn ich 500 Variablen füllen muss, muss ich das ob ich Ctors habe oder nicht.
Abstraktion kostet keine Performance in C++.