Wenn ein Computer eine Variable speichert und ein Programm den Wert der Variablen abrufen muss, woher weiß der Computer, wo im Speicher nach dem Wert dieser Variablen gesucht werden muss?
compilers
memory-access
variable-binding
MCMastery
quelle
quelle
Antworten:
Ich würde vorschlagen, dass Sie in die wundervolle Welt des Compiler-Aufbaus schauen! Die Antwort ist, dass es ein komplizierter Prozess ist.
Denken Sie daran, dass Variablennamen nur dem Programmierer zuliebe vorhanden sind, um Ihnen eine Vorstellung zu geben. Der Computer verwandelt am Ende alles in Adressen.
Lokale Variablen werden (im Allgemeinen) auf dem Stack gespeichert, dh sie sind Teil der Datenstruktur, die einen Funktionsaufruf darstellt. Wir können die vollständige Liste der Variablen bestimmen, die eine Funktion (möglicherweise) verwenden wird, indem wir diese Funktion betrachten, damit der Compiler sehen kann, wie viele Variablen für diese Funktion benötigt werden und wie viel Platz jede Variable benötigt.
Es gibt ein bisschen Magie, den Stapelzeiger, ein Register, in dem immer die Adresse gespeichert ist, an der der aktuelle Stapel beginnt.
Jede Variable erhält einen "Stack-Offset", in dem sie im Stack gespeichert ist. Wenn das Programm auf eine Variable zugreifen muss
x
, wird diese durch den Compiler ersetzt , um den tatsächlichen physischen Speicherortx
zu ermittelnSTACK_POINTER + x_offset
, an dem sie gespeichert ist.Beachten Sie, dass Sie aus diesem Grund einen Zeiger zurückbekommen, wenn Sie
malloc
odernew
in C oder C ++ verwenden. Sie können nicht feststellen, wo genau sich ein Heap-allokierter Wert im Speicher befindet. Sie müssen also einen Zeiger darauf behalten. Dieser Zeiger befindet sich auf dem Stapel, zeigt jedoch auf den Haufen.Die Details zum Aktualisieren von Stacks für Funktionsaufrufe und Retouren sind kompliziert. Wenn Sie interessiert sind, würde ich das Drachenbuch oder das Tigerbuch empfehlen .
quelle
Das Programm sagt es. Computer haben von Haus aus kein Konzept von "Variablen" - das ist eine reine Hochsprache!
Hier ist ein C-Programm:
und hier ist der Assembler-Code, den es kompiliert: (Kommentare beginnend mit
;
)Für "int a = 1;" Die CPU sieht den Befehl "Speichere den Wert 1 an der Adresse (Wert des Registers rbp, minus 4)". Es weiß, wo der Wert 1 gespeichert werden muss, da das Programm dies mitteilt.
In ähnlicher Weise sagt der nächste Befehl "lade den Wert an der Adresse (Wert des Registers rbp, minus 4) in das Register eax". Der Computer muss nichts über Variablen wissen.
quelle
%rsp
ist dies der Stapelzeiger der CPU.%rbp
ist ein Register, das sich auf das Bit des Stapels bezieht, das von der aktuellen Funktion verwendet wird. Die Verwendung von zwei Registern vereinfacht das Debuggen.Wenn der Compiler oder Interpreter auf die Deklaration einer Variablen stößt, entscheidet er, welche Adresse zum Speichern dieser Variablen verwendet wird, und zeichnet die Adresse dann in einer Symboltabelle auf. Wenn nachfolgende Verweise auf diese Variable gefunden werden, wird die Adresse aus der Symboltabelle ersetzt.
Die in der Symboltabelle aufgezeichnete Adresse kann ein Versatz von einem Register (wie dem Stapelzeiger) sein, dies ist jedoch ein Implementierungsdetail.
quelle
Die genauen Methoden hängen davon ab, worüber Sie konkret sprechen und wie tief Sie gehen möchten. Das Speichern von Dateien auf einer Festplatte unterscheidet sich beispielsweise vom Speichern von Daten im Arbeitsspeicher oder vom Speichern von Daten in einer Datenbank. Obwohl die Konzepte ähnlich sind. Und wie Sie dies auf Programmierebene tun, ist eine andere Erklärung als die eines Computers auf E / A-Ebene.
Die meisten Systeme verwenden eine Art Verzeichnis- / Index- / Registrierungsmechanismus, damit der Computer die Daten finden und darauf zugreifen kann. Dieser Index / dieses Verzeichnis enthält einen oder mehrere Schlüssel und die Adresse, in der sich die Daten tatsächlich befinden (Festplatte, RAM, Datenbank usw.).
Computerprogramm-Beispiel
Ein Computerprogramm kann auf verschiedene Arten auf den Speicher zugreifen. Typischerweise gibt das Betriebssystem dem Programm einen Adressraum und das Programm kann mit diesem Adressraum machen, was es will. Es kann direkt an eine beliebige Adresse in seinem Speicherbereich schreiben, und es kann verfolgen, wie es will. Dies hängt manchmal von der Programmiersprache und dem Betriebssystem oder sogar von den bevorzugten Techniken des Programmierers ab.
Wie in einigen anderen Antworten erwähnt, unterscheidet sich die genaue verwendete Codierung oder Programmierung, aber normalerweise wird hinter den Kulissen so etwas wie ein Stapel verwendet. Es hat ein Register, das den Speicherort speichert, an dem der aktuelle Stapel beginnt, und dann eine Methode, um zu wissen, wo sich in diesem Stapel eine Funktion oder Variable befindet.
In vielen höheren Programmiersprachen erledigt es all das für Sie. Alles, was Sie tun müssen, ist, eine Variable zu deklarieren und etwas in dieser Variablen zu speichern, und es werden die erforderlichen Stapel und Arrays hinter den Kulissen für Sie erstellt.
In Anbetracht der Vielseitigkeit der Programmierung gibt es jedoch nicht wirklich eine Antwort, da ein Programmierer jederzeit direkt an eine beliebige Adresse innerhalb des zugewiesenen Speicherplatzes schreiben kann (vorausgesetzt, er verwendet eine Programmiersprache, die dies zulässt). Dann könnte er seine Position in einem Array speichern oder es sogar nur hart im Programm codieren (dh die Variable "alpha" wird immer am Anfang des Stapels oder immer in den ersten 32 Bits des zugewiesenen Speichers gespeichert).
Zusammenfassung
Im Grunde genommen muss es also einen Mechanismus hinter den Kulissen geben, der dem Computer mitteilt, wo Daten gespeichert sind. Eine der beliebtesten Methoden ist eine Art Index / Verzeichnis, das Schlüssel und die Speicheradresse enthält. Dies wird auf viele Arten implementiert und normalerweise vom Benutzer (und manchmal sogar vom Programmierer) gekapselt.
Referenz: Wie merken sich Computer, wo sie Dinge aufbewahren?
quelle
Es weiß wegen Vorlagen und Formaten.
Das Programm / die Funktion / der Computer wissen eigentlich nicht, wo sich etwas befindet. Es erwartet nur, dass sich etwas an einem bestimmten Ort befindet. Nehmen wir ein Beispiel.
Unsere neue Klasse 'simpleClass' enthält 3 wichtige Variablen - zwei Ganzzahlen, die bei Bedarf Daten enthalten können, und einen Zeiger auf ein anderes 'simpleClass-Objekt'. Nehmen wir an, wir arbeiten der Einfachheit halber auf einem 32-Bit-Computer. 'gcc' oder ein anderer 'C'-Compiler würde eine Vorlage erstellen, mit der wir arbeiten können, um einige Daten zuzuweisen.
Einfache Typen
Erstens, wenn man ein Schlüsselwort für einen einfachen Typ wie 'int' verwendet, macht der Compiler im Abschnitt '.data' oder '.bss' der ausführbaren Datei eine Notiz, so dass die Daten, wenn sie vom Betriebssystem ausgeführt werden, sind dem Programm zur Verfügung. Das Schlüsselwort 'int' weist 4 Bytes (32 Bit) zu, während ein 'long int' 8 Bytes (64 Bit) zuweist.
Manchmal kann eine Variable zellenweise direkt nach dem Befehl kommen, der sie in den Speicher laden soll. In Pseudo-Assembler sieht das also so aus:
Dies würde mit dem Wert '5' sowohl in EAX als auch in EBX enden.
Während das Programm ausgeführt wird, wird jede Anweisung mit Ausnahme der '5' ausgeführt, da das unmittelbare Laden auf sie verweist und die CPU veranlasst, darüber zu springen.
Der Nachteil dieser Methode ist, dass sie nur für Konstanten wirklich praktisch ist, da es unpraktisch wäre, Arrays / Puffer / Strings in der Mitte Ihres Codes zu belassen. Im Allgemeinen werden die meisten Variablen in Programmköpfen gespeichert.
Wenn auf eine dieser dynamischen Variablen zugegriffen werden muss, kann der unmittelbare Wert wie ein Zeiger behandelt werden:
Dies würde mit dem Wert '0x0AF2CE66' im Register EAX und dem Wert '5' im Register EBX enden. Man kann auch Werte in Registern addieren, so dass wir mit dieser Methode Elemente eines Arrays oder Strings finden können.
Ein weiterer wichtiger Punkt ist, dass man Werte speichern kann, wenn Adressen auf ähnliche Weise verwendet werden, damit man später auf die Werte in diesen Zellen verweisen kann.
Komplexe Typen
Wenn wir zwei Objekte dieser Klasse erstellen:
dann können wir dem dafür im ersten Objekt verfügbaren Feld einen Zeiger auf das zweite Objekt zuweisen:
Jetzt kann das Programm erwarten, die Adresse des zweiten Objekts im Zeigerfeld des ersten Objekts zu finden. In Erinnerung würde dies ungefähr so aussehen:
Eine sehr wichtige Tatsache ist, dass 'newObjA' und 'newObjB' beim Kompilieren keine Namen haben. Es sind nur Orte, an denen wir Daten erwarten. Wenn wir also 2 Zellen zu & newObjA hinzufügen, finden wir die Zelle, die als 'nextObject' fungiert. Wenn wir also die Adresse von 'newObjA' kennen und die Zelle 'nextObject' relativ dazu ist, können wir die Adresse von 'newObjB' kennen:
Dies würde mit '2 + & newObjA' in 'EAX' und '& newObjB' in 'EBX' enden.
Vorlagen / Formate
Wenn der Compiler die Klassendefinition kompiliert, kompiliert er tatsächlich eine Möglichkeit, ein Format zu erstellen, in ein Format zu schreiben und aus einem Format zu lesen.
Das obige Beispiel ist eine Vorlage für eine einfach verknüpfte Liste mit zwei 'int'-Variablen. Diese Arten von Konstruktionen sind sehr wichtig für die dynamische Speicherzuweisung, zusammen mit binären und n-fachen Bäumen. Praktische Anwendungen von n-ary-Bäumen wären Dateisysteme, die aus Verzeichnissen bestehen, die auf Dateien, Verzeichnisse oder andere Instanzen verweisen, die von Treibern / dem Betriebssystem erkannt werden.
Um auf alle Elemente zugreifen zu können, müssen Sie sich vorstellen, wie sich ein Inchworm in der Struktur auf und ab bewegt. Auf diese Weise weiß das Programm / die Funktion / der Computer nichts, sondern führt nur Anweisungen zum Verschieben von Daten aus.
quelle