Zeiger auf lokale Variable auflösen
-
Hi,
#include <iostream> int* f() { int x=10; return &x; } int main() { std::cout<< * f() <<std::endl; }kann mir jemand erklären, warum der Code überhaupt funktioniert? Die Funktion ermittelt die Adresse einer lokalen Variablen und mit "* f()" kann ich den Zeiger darauf wieder dereferenzieren, obwohl die Variable doch schon zerstört sein müsste. Es wird komischerweise jedoch lediglich eine Warnung ausgegeben:
In function ‘int* f()’:
warning: address of local variable ‘x’ returnedDanke

-
zufall das es funktioniert
-
^^ schrieb:
zufall das es funktioniert
Jap.
Es KANN funktionieren, muss aber nicht.Es funktioniert noch so einiges, aber das heisst nicht, dass es das immer tut.

-
Interessant. Ich dachte nur, dass der g++ (4.2) bei solchen einfachen Dingen standardkonform bleiben würde.
-
moagnus schrieb:
Interessant. Ich dachte nur, dass der g++ (4.2) bei solchen einfachen Dingen standardkonform bleiben würde.
und?
-
moagnus schrieb:
Interessant. Ich dachte nur, dass der g++ (4.2) bei solchen einfachen Dingen standardkonform bleiben würde.
tut er doch auch?
-
Naja, wenns Zufall ist, ists kein Standardverhalten, oder?
-
moagnus schrieb:
Naja, wenns Zufall ist, ists kein Standardverhalten, oder?
In C++ gibt es einiges das undefiniert ist, aber deshalb ist es dennoch hat das nichts mit Standardkonformität zu tun (C++ Standard).
-
Ok, wieder was gelernt.

Aber wird das dann nicht ein bisschen inkonsistent mit der Speicherverwaltung?
-
moagnus schrieb:
Ok, wieder was gelernt.

Aber wird das dann nicht ein bisschen inkonsistent mit der Speicherverwaltung?Wieso? C++ ist hier sehr konsequent: Dein Fall ist undefiniert (Das Objekt auf das der Zeiger zeigt existiert nicht mehr, und in deinem Fall wurde der Speicher einfach seit dem Freigeben nicht wieder überschrieben; Sprich du greifst auf Datenmüll zu der zufälligerweise noch dem alten Stand entspricht).
Speicher wird nicht nach der Freigabe ausgenullt oder so.
cu André
-
Gut, danke für die Erklärung.
-
Es geht sogar noch ein bisschen weiter; mit der Freigabe des Speichers wurde der Zeiger(-wert) selbst ungültig - jede Verwendung dieses Wertes (z.B. Speichern in einer Zeigervariable) resultiert für sich bereits in undefiniertem Verhalten.
-
Soweit ichs verstanden habe, kann der Speicher nach Funktionsende und damit nach "Zerstörung" der lokalen Variablen wieder andersweitig vom BS verwendet und beschrieben werden. Wenn ich jetzt mit dem Zeiger auf diesen Inhalt zugreife, benutze ich nicht mehr den Wert, den ich eigentlich benutzen will. Also ist das Verhalten undefiniert. Evtl. steht an der Stelle nicht mal mehr ein int. Dann stimmt auch der int-Zeiger vom Typ her nicht mehr überein. Meinst du das?
-
würdest du im debug-Modus kompilieren, müsste so eine Handlung zu einem Segmentation Fault führen, und das Programm beendet werden. Das sich der Wert noch nicht geänder hat, ist kein zufall, sondern nicht anders Möglich, denn es wurde seit der Freigabe des Speichers noch keine neue Lokale Variable angelegt. Würdest du bevor du die Variable ausgibst noch eine Weitere initialisieren müsste sich der Wert auch änders. Der Speicer kann nicht von anderen Programmen belegt werden, weil es sich um den Stack handelt.
-
Ach, du hast Recht. Es gibt ja nen gekapselten Adressraum für das Programm.
-
moagnus schrieb:
Soweit ichs verstanden habe, kann der Speicher nach Funktionsende und damit nach "Zerstörung" der lokalen Variablen wieder andersweitig vom BS verwendet und beschrieben werden. Wenn ich jetzt mit dem Zeiger auf diesen Inhalt zugreife, benutze ich nicht mehr den Wert, den ich eigentlich benutzen will. Also ist das Verhalten undefiniert. Evtl. steht an der Stelle nicht mal mehr ein int. Dann stimmt auch der int-Zeiger vom Typ her nicht mehr überein. Meinst du das?
Das ist eine mögliche Variante von undefiniert, kommt aber nicht zum eigentlichen Punkt. Jede Verwendung des Zeigerwertes (und da der Rückgabewert der Funktion ein rvalue ist bedeutet das hier: jede beliebige Operation mit dem Funktionsaufruf, die diesen Wert nicht verwirft (=cast nach void, ,-Operator, Nicht-Verwendung des Funktionswertes) ist undefiniert. Wie eine konkrete Implementation hier reagiert, stellt dann nur eine Möglichkeit dar. Es gibt einen praktisch bedeutsamen analogen Fall:
int* p = new int; delete p;Visual C++ (wie wohl auch andere Compiler) verändert im Debug-Modus beim delete das Zeigerobjekt p (das ist erlaubt, obwohl diese Veränderung durchaus beobachtbar sein kann - wir könnten ja p einfach als rohen Speicher anfassen und untersuchen, per memcmp und Ähnlichem). Der Wert, den p dann erhält, zeigt üblicherweise auf nicht existenten Speicher um dann eine Zugriffsverletzung auszulösen. Der Punkt dabei ist, dass der Wert p nach dem delete nichts mit dem davor zu tun hat. Ein vergleichbarer Vorgang (sogar problemloser, da wir hier nicht auf die Repräsentation zugreifen können) ist durchaus bei der Rückkehr aus der Funktion denkbar. Der bloße Versuch, diesen Wert z.B. zu dereferenzieren ist dann schon problematisch, zum Lesen irgendeines Wertes kommen wir gar nicht erst. Und das ist nur eine Möglichkeit, wie eine Implementation hier reagieren könnte (eine Andere wäre z.B. bei einem Interpreter, dass der Wert der Gleiche ist, aber nicht mehr mit einer realen Adresse verknüpft ist - und wenn ich länger drüber nachdenke, fallen mir sicher noch mehr Szenarien ein).
-
Ich versteh leider immer noch nicht ganz, was du meinst.
Was ich verstanden zu haben glaube: Compiliert man dein Code-Beispiel im Debug-Modus, wird die Speicheradresse geändert, die in p abgelegt ist. Deswegen macht Dereferenzierung keinen Sinn.
-
Also camper, dass eine Implementierung den Wert von "p" ändern darf beim delete ist mir neu. MSVC macht das auch *nicht*. MSVC überschreibt nur den Speicher der freigegeben wurde mit 0xDD, allerdings nicht die Zeigervariable selbst.
Und wie gesagt, ich glaube auch nicht dass das legal wäre.
(Davon abgesehen dass es kaum noch sinnvolle Dinge gibt die man mit "p" machen kann nach dem delete)
-
hustbaer schrieb:
Also camper, dass eine Implementierung den Wert von "p" ändern darf beim delete ist mir neu. MSVC macht das auch *nicht*. MSVC überschreibt nur den Speicher der freigegeben wurde mit 0xDD, allerdings nicht die Zeigervariable selbst.
bin mir ziemlich sicher, das die debugging-rtl nach dem delete den pointer auf 0xfeeefeee zeigen lässt.
-
du irrst schrieb:
hustbaer schrieb:
Also camper, dass eine Implementierung den Wert von "p" ändern darf beim delete ist mir neu. MSVC macht das auch *nicht*. MSVC überschreibt nur den Speicher der freigegeben wurde mit 0xDD, allerdings nicht die Zeigervariable selbst.
bin mir ziemlich sicher, das die debugging-rtl nach dem delete den pointer auf 0xfeeefeee zeigen lässt.
Ja? Also bei mir wird der Speicher angefasst und auf 0xFEEE gesetzt, und nicht der Pointer selbst.