Wie kann es sein? Ist der Speicher einer lokalen Variablen außerhalb ihrer Funktion nicht unzugänglich?
Sie mieten ein Hotelzimmer. Sie legen ein Buch in die oberste Schublade des Nachttisches und schlafen ein. Sie checken am nächsten Morgen aus, aber "vergessen", Ihren Schlüssel zurückzugeben. Du stiehlst den Schlüssel!
Eine Woche später kehren Sie ins Hotel zurück, checken nicht ein, schleichen sich mit Ihrem gestohlenen Schlüssel in Ihr altes Zimmer und schauen in die Schublade. Dein Buch ist noch da. Erstaunlich!
Wie kann das sein? Ist der Inhalt einer Hotelzimmerschublade nicht unzugänglich, wenn Sie das Zimmer nicht gemietet haben?
Nun, offensichtlich kann dieses Szenario in der realen Welt kein Problem sein. Es gibt keine mysteriöse Kraft, die dazu führt, dass Ihr Buch verschwindet, wenn Sie nicht mehr berechtigt sind, im Raum zu sein. Es gibt auch keine mysteriöse Kraft, die Sie daran hindert, einen Raum mit einem gestohlenen Schlüssel zu betreten.
Die Hotelleitung ist nicht verpflichtet , Ihr Buch zu entfernen. Sie haben mit ihnen keinen Vertrag geschlossen, der besagt, dass sie Dinge für Sie zerreißen, wenn Sie sie zurücklassen. Wenn Sie illegal Ihr Zimmer mit einem gestohlenen Schlüssel erneut eingeben es zurück zu bekommen, ist das Hotel Sicherheitspersonal nicht erforderlich fangen Sie schleichen in. Sie haben keinen Vertrag mit ihnen machen, der „sagte , wenn ich versuche , meine zu schleichen zurück in Zimmer später müssen Sie mich aufhalten. " Sie haben vielmehr einen Vertrag mit ihnen unterzeichnet, der besagt: "Ich verspreche, mich später nicht mehr in mein Zimmer zu schleichen", einen Vertrag, den Sie gebrochen haben .
In dieser Situation kann alles passieren . Das Buch kann da sein - du hast Glück gehabt. Das Buch eines anderen kann dort sein und Ihr Buch könnte im Ofen des Hotels sein. Jemand könnte genau dort sein, wenn Sie hereinkommen und Ihr Buch in Stücke reißen. Das Hotel hätte den Tisch und das Buch komplett entfernen und durch einen Kleiderschrank ersetzen können. Das gesamte Hotel könnte gerade abgerissen und durch ein Fußballstadion ersetzt werden, und Sie werden bei einer Explosion sterben, während Sie sich herumschleichen.
Sie wissen nicht, was passieren wird; Als Sie aus dem Hotel auscheckten und einen Schlüssel stahlen, um ihn später illegal zu verwenden, gaben Sie das Recht auf, in einer vorhersehbaren, sicheren Welt zu leben, weil Sie sich entschieden hatten, gegen die Regeln des Systems zu verstoßen.
C ++ ist keine sichere Sprache . Es wird Ihnen fröhlich erlauben, die Regeln des Systems zu brechen. Wenn Sie versuchen, etwas Illegales und Dummes zu tun, wie in einen Raum zurückzukehren, in dem Sie nicht autorisiert sind, und durch einen Schreibtisch zu stöbern, der möglicherweise nicht mehr vorhanden ist, wird C ++ Sie nicht aufhalten. Sicherere Sprachen als C ++ lösen dieses Problem, indem Sie Ihre Leistung einschränken - beispielsweise durch eine viel strengere Kontrolle über Schlüssel.
AKTUALISIEREN
Heilige Güte, diese Antwort bekommt viel Aufmerksamkeit. (Ich bin mir nicht sicher warum - ich hielt es nur für eine "lustige" kleine Analogie, aber was auch immer.)
Ich dachte, es wäre vielleicht sinnvoll, dies mit ein paar weiteren technischen Gedanken ein wenig zu aktualisieren.
Compiler generieren Code, der die Speicherung der von diesem Programm manipulierten Daten verwaltet. Es gibt viele verschiedene Möglichkeiten, Code zum Verwalten des Speichers zu generieren, aber im Laufe der Zeit haben sich zwei grundlegende Techniken festgesetzt.
Die erste besteht darin, eine Art "langlebigen" Speicherbereich zu haben, in dem die "Lebensdauer" jedes Bytes im Speicher - dh der Zeitraum, in dem es einer Programmvariablen gültig zugeordnet ist - nicht einfach vorhergesagt werden kann von Zeit. Der Compiler generiert Aufrufe an einen "Heap-Manager", der weiß, wie Speicher dynamisch zugewiesen wird, wenn er benötigt wird, und ihn zurückfordert, wenn er nicht mehr benötigt wird.
Die zweite Methode besteht darin, einen "kurzlebigen" Speicherbereich zu haben, in dem die Lebensdauer jedes Bytes bekannt ist. Hier folgen die Lebensdauern einem „Verschachtelungsmuster“. Die langlebigsten dieser kurzlebigen Variablen werden vor allen anderen kurzlebigen Variablen zugewiesen und zuletzt freigegeben. Variablen mit kürzerer Lebensdauer werden nach den Variablen mit der längsten Lebensdauer zugewiesen und vor ihnen freigegeben. Die Lebensdauer dieser kurzlebigen Variablen ist innerhalb der Lebensdauer längerlebiger Variablen „verschachtelt“.
Lokale Variablen folgen dem letzteren Muster; Wenn eine Methode eingegeben wird, werden ihre lokalen Variablen lebendig. Wenn diese Methode eine andere Methode aufruft, werden die lokalen Variablen der neuen Methode lebendig. Sie sind tot, bevor die lokalen Variablen der ersten Methode tot sind. Die relative Reihenfolge der Anfänge und Enden der Lebensdauer von Speichern, die mit lokalen Variablen verknüpft sind, kann im Voraus ermittelt werden.
Aus diesem Grund werden lokale Variablen normalerweise als Speicher in einer "Stapel" -Datenstruktur generiert, da ein Stapel die Eigenschaft hat, dass das erste, was darauf gedrückt wird, das letzte ist, das herausspringt.
Es ist, als ob das Hotel beschließt, Zimmer nur nacheinander zu vermieten, und Sie können erst auschecken, wenn alle Zimmer eine höhere Zimmernummer haben als Sie.
Denken wir also über den Stapel nach. In vielen Betriebssystemen erhalten Sie einen Stapel pro Thread und der Stapel wird einer bestimmten festen Größe zugewiesen. Wenn Sie eine Methode aufrufen, werden Inhalte auf den Stapel verschoben. Wenn Sie dann einen Zeiger auf den Stapel aus Ihrer Methode zurückgeben, wie dies hier im Originalposter der Fall ist, ist dies nur ein Zeiger auf die Mitte eines vollständig gültigen Millionen-Byte-Speicherblocks. In unserer Analogie checken Sie aus dem Hotel aus; Wenn Sie dies tun, haben Sie gerade aus dem am höchsten belegten Raum ausgecheckt. Wenn niemand anderes nach Ihnen eincheckt und Sie illegal in Ihr Zimmer zurückkehren, sind alle Ihre Sachen garantiert immer noch in diesem bestimmten Hotel vorhanden .
Wir verwenden Stapel für temporäre Geschäfte, weil sie wirklich billig und einfach sind. Eine Implementierung von C ++ ist nicht erforderlich, um einen Stapel zum Speichern von Einheimischen zu verwenden. es könnte den Haufen gebrauchen. Dies ist nicht der Fall, da dies das Programm verlangsamen würde.
Eine Implementierung von C ++ ist nicht erforderlich, um den Müll, den Sie auf dem Stapel gelassen haben, unberührt zu lassen, damit Sie später illegal darauf zurückkommen können. Es ist völlig legal, dass der Compiler Code generiert, der alles in dem "Raum", den Sie gerade verlassen haben, auf Null zurücksetzt. Es ist nicht so, denn das wäre wieder teuer.
Eine Implementierung von C ++ ist nicht erforderlich, um sicherzustellen, dass beim logischen Verkleinern des Stapels die früher gültigen Adressen weiterhin im Speicher zugeordnet werden. Die Implementierung darf dem Betriebssystem mitteilen, dass "wir diese Stapelseite jetzt nicht mehr verwenden. Bis ich etwas anderes sage, geben Sie eine Ausnahme aus, die den Prozess zerstört, wenn jemand die zuvor gültige Stapelseite berührt." Wiederum tun Implementierungen dies nicht wirklich, weil es langsam und unnötig ist.
Stattdessen können Sie mit Implementierungen Fehler machen und damit durchkommen. Meistens. Bis eines Tages etwas wirklich Schreckliches schief geht und der Prozess explodiert.
Das ist problematisch. Es gibt viele Regeln und es ist sehr leicht, sie versehentlich zu brechen. Ich habe sicherlich viele Male. Und schlimmer noch, das Problem tritt oft nur dann auf, wenn festgestellt wird, dass der Speicher Milliarden von Nanosekunden nach der Korruption beschädigt ist, wenn es sehr schwer ist herauszufinden, wer ihn durcheinander gebracht hat.
Mehr speichersichere Sprachen lösen dieses Problem, indem sie Ihre Leistung einschränken. In "normalem" C # gibt es einfach keine Möglichkeit, die Adresse eines Einheimischen zu übernehmen und zurückzugeben oder für später zu speichern. Sie können die Adresse eines Einheimischen verwenden, aber die Sprache ist so konzipiert, dass es unmöglich ist, sie nach Ablauf der Lebensdauer des Orts zu verwenden. Um die Adresse eines lokalen Objekts zu übernehmen und zurückzugeben, müssen Sie den Compiler in einen speziellen "unsicheren" Modus versetzen und das Wort "unsicher" in Ihr Programm aufnehmen, um auf die Tatsache aufmerksam zu machen, dass Sie dies wahrscheinlich tun etwas Gefährliches, das gegen die Regeln verstoßen könnte.
Zur weiteren Lektüre:
address of local variable ‘a’ returned
. Valgrind ShowsInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr