Wie erinnern sich Computer daran, wo sie Dinge aufbewahren?


32

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?


17
Das tut es nicht. "Der Computer" ist völlig ahnungslos. Wir müssen alle Adressen fest codieren. (Was ein bisschen vereinfacht, aber nicht zu sehr.)
Raphael

1
@Raphael: Verallgemeinern wir das auf "Wir müssen Basisadressen hart codieren".
Phresnel

Jedes Mal, wenn Sie eine Variable deklarieren, enthält das Programm, das für die Ausführung Ihres Codes verantwortlich ist, den Variablennamen mit seiner Adresse in einer Hash-Tabelle (auch bekannt als Namespace). Ich würde empfehlen, das Buch "Struktur und Implementierung von Computerprogrammen (SICP)" zu lesen, um sich mit diesen kleinen Details vertraut zu machen.
Abhirath Mahipal

Ihr Quellprogramm verwendet eine Variable. Der Compiler oder Interpreter entscheidet, wie er es implementiert: Er generiert Anweisungen, die der Computer ausführen soll, und muss sicherstellen, dass diese Anweisungen Werte von den Stellen abrufen, an denen vorherige Anweisungen sie gespeichert haben.
PJTraill

1
@AbhirathMahipal: Eine Variable muss zur Kompilierungs- oder Laufzeit keine Adresse haben. "Namespace" ist ein Sprachkonzept, während eine Tabelle (gehasht oder anderweitig) ein Implementierungsdetail ist. Der Name muss beim Ausführen im Programm beibehalten werden.
PJTraill

Antworten:


31

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 Speicherort xzu ermitteln STACK_POINTER + x_offset, an dem sie gespeichert ist.

Beachten Sie, dass Sie aus diesem Grund einen Zeiger zurückbekommen, wenn Sie mallocoder newin 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 .


24

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?

Das Programm sagt es. Computer haben von Haus aus kein Konzept von "Variablen" - das ist eine reine Hochsprache!

Hier ist ein C-Programm:

int main(void)
{
    int a = 1;
    return a + 3;
}

und hier ist der Assembler-Code, den es kompiliert: (Kommentare beginnend mit ;)

main:
    ; {
    pushq   %rbp
    movq    %rsp, %rbp

    ; int a = 1
    movl    $1, -4(%rbp)

    ; return a + 3
    movl    -4(%rbp), %eax
    addl    $3, %eax

    ; }
    popq    %rbp
    ret

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.


2
Um dies mit der Antwort von jmite zu verbinden, %rspist dies der Stapelzeiger der CPU. %rbpist 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.
MSalters

2

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.


0

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?


0

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.

class simpleClass{
    public:
        int varA=58;
        int varB=73;
        simpleClass* nextObject=NULL;
};

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:

...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...

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:

...
clear register EAX
clear register EBX
load the immediate value into EAX
0x0AF2CE66 (Let's say this is the address of a cell containing '5')
load the value pointed to by EAX into EBX
...

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:

simpleClass newObjA;
simpleClass newObjB;

dann können wir dem dafür im ersten Objekt verfügbaren Feld einen Zeiger auf das zweite Objekt zuweisen:

newObjA.nextObject=&newObjB;

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:

newObjA:    58
            73
            &newObjB
            ...
newObjB:    58
            73
            NULL

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:

...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX

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.


Die hier verwendeten Wörter 'template' und 'format' tauchen in keinem Compiler oder Compiler-Lehrbuch auf, das ich jemals gesehen habe, und es scheint keinen Grund zu geben, beide Wörter für dieselbe nicht vorhandene Sache zu verwenden. Variablen haben Adressen und / oder Offsets, das ist alles, was Sie wissen müssen.
User207421

Ich verwende die Wörter, da sie Abstraktionen für die Datenanordnung sind, genau wie Zahlen, Dateien, Arrays und Variablen Abstraktionen sind.
Mr. Minty Fresh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.