Compiler-Design - Laufzeitumgebung
Ein Programm als Quellcode ist lediglich eine Sammlung von Text (Code, Anweisungen usw.). Um es lebendig zu machen, müssen Aktionen auf dem Zielcomputer ausgeführt werden. Ein Programm benötigt Speicherressourcen, um Anweisungen auszuführen. Ein Programm enthält Namen für Prozeduren, Bezeichner usw., die zur Laufzeit dem tatsächlichen Speicherort zugeordnet werden müssen.
Mit Laufzeit meinen wir ein Programm in Ausführung. Die Laufzeitumgebung ist ein Status des Zielcomputers, der Softwarebibliotheken, Umgebungsvariablen usw. enthalten kann, um Dienste für die im System ausgeführten Prozesse bereitzustellen.
Das Laufzeit-Support-System ist ein Paket, das hauptsächlich mit dem ausführbaren Programm selbst generiert wird und die Prozesskommunikation zwischen dem Prozess und der Laufzeitumgebung erleichtert. Es kümmert sich um die Speicherzuweisung und -entzuweisung, während das Programm ausgeführt wird.
Aktivierungsbäume
Ein Programm ist eine Folge von Anweisungen, die zu einer Reihe von Prozeduren zusammengefasst sind. Anweisungen in einer Prozedur werden nacheinander ausgeführt. Eine Prozedur hat ein Start- und ein Endtrennzeichen, und alles, was sich darin befindet, wird als Hauptteil der Prozedur bezeichnet. Die Prozedurkennung und die Reihenfolge der darin enthaltenen endlichen Anweisungen bilden den Hauptteil der Prozedur.
Die Ausführung einer Prozedur wird als Aktivierung bezeichnet. Ein Aktivierungsdatensatz enthält alle erforderlichen Informationen, um eine Prozedur aufzurufen. Ein Aktivierungsdatensatz kann die folgenden Einheiten enthalten (abhängig von der verwendeten Ausgangssprache).
Temporäre | Speichert temporäre und Zwischenwerte eines Ausdrucks. |
Lokale Daten | Speichert lokale Daten der aufgerufenen Prozedur. |
Maschinenstatus | Speichert den Maschinenstatus wie Register, Programmzähler usw., bevor die Prozedur aufgerufen wird. |
Steuerverbindung | Speichert die Adresse des Aktivierungsdatensatzes der Anruferprozedur. |
Zugriffslink | Speichert die Informationen von Daten, die außerhalb des lokalen Bereichs liegen. |
Aktuelle Parameter | Speichert tatsächliche Parameter, dh Parameter, die zum Senden von Eingaben an die aufgerufene Prozedur verwendet werden. |
Rückgabewert | Speichert Rückgabewerte. |
Immer wenn eine Prozedur ausgeführt wird, wird ihr Aktivierungsdatensatz auf dem Stapel gespeichert, der auch als Kontrollstapel bezeichnet wird. Wenn eine Prozedur eine andere Prozedur aufruft, wird die Ausführung des Aufrufers angehalten, bis die aufgerufene Prozedur die Ausführung beendet. Zu diesem Zeitpunkt wird der Aktivierungsdatensatz der aufgerufenen Prozedur auf dem Stapel gespeichert.
Wir gehen davon aus, dass die Programmsteuerung sequentiell abläuft und beim Aufruf einer Prozedur ihre Steuerung auf die aufgerufene Prozedur übertragen wird. Wenn eine aufgerufene Prozedur ausgeführt wird, gibt sie die Steuerung an den Aufrufer zurück. Diese Art des Kontrollflusses erleichtert die Darstellung einer Reihe von Aktivierungen in Form eines Baums, der alsactivation tree.
Um dieses Konzept zu verstehen, nehmen wir ein Stück Code als Beispiel:
. . .
printf(“Enter Your Name: “);
scanf(“%s”, username);
show_data(username);
printf(“Press any key to continue…”);
. . .
int show_data(char *user)
{
printf(“Your name is %s”, username);
return 0;
}
. . .
Unten ist der Aktivierungsbaum des angegebenen Codes.
Jetzt verstehen wir, dass Prozeduren in der Tiefe zuerst ausgeführt werden, daher ist die Stapelzuweisung die am besten geeignete Speicherform für Prozeduraktivierungen.
Speicherzuordnung
Die Laufzeitumgebung verwaltet die Anforderungen an den Laufzeitspeicher für die folgenden Entitäten:
Code: Es ist als Textteil eines Programms bekannt, der sich zur Laufzeit nicht ändert. Der Speicherbedarf ist zur Kompilierungszeit bekannt.
Procedures: Ihr Textteil ist statisch, aber sie werden zufällig aufgerufen. Aus diesem Grund wird der Stapelspeicher zum Verwalten von Prozeduraufrufen und Aktivierungen verwendet.
Variables: Variablen sind nur zur Laufzeit bekannt, es sei denn, sie sind global oder konstant. Das Heap-Speicherzuweisungsschema wird zum Verwalten der Zuweisung und Aufhebung der Zuweisung von Speicher für Variablen zur Laufzeit verwendet.
Statische Zuordnung
In diesem Zuordnungsschema sind die Kompilierungsdaten an einen festen Ort im Speicher gebunden und ändern sich nicht, wenn das Programm ausgeführt wird. Da der Speicherbedarf und die Speicherorte im Voraus bekannt sind, ist kein Laufzeitunterstützungspaket für die Speicherzuweisung und -freigabe erforderlich.
Stapelzuordnung
Prozeduraufrufe und deren Aktivierung werden über die Stapelspeicherzuordnung verwaltet. Es funktioniert in der LIFO-Methode (Last-In-First-Out) und diese Zuordnungsstrategie ist sehr nützlich für rekursive Prozeduraufrufe.
Heap-Zuordnung
Lokale Variablen für eine Prozedur werden nur zur Laufzeit zugewiesen und freigegeben. Die Heap-Zuweisung wird verwendet, um den Variablen dynamisch Speicher zuzuweisen und ihn zurückzufordern, wenn die Variablen nicht mehr benötigt werden.
Mit Ausnahme des statisch zugewiesenen Speicherbereichs können sowohl der Stapel- als auch der Heapspeicher dynamisch und unerwartet wachsen und schrumpfen. Daher können sie nicht mit einer festen Speichermenge im System versehen werden.
Wie in der obigen Abbildung gezeigt, wird dem Textteil des Codes eine feste Speichermenge zugewiesen. Stapel- und Heapspeicher sind an den Extremen des dem Programm zugewiesenen Gesamtspeichers angeordnet. Beide schrumpfen und wachsen gegeneinander.
Parameterübergabe
Das Kommunikationsmedium zwischen Prozeduren ist als Parameterübergabe bekannt. Die Werte der Variablen aus einer aufrufenden Prozedur werden durch einen Mechanismus in die aufgerufene Prozedur übertragen. Bevor Sie fortfahren, gehen Sie zunächst einige grundlegende Terminologien durch, die sich auf die Werte in einem Programm beziehen.
r-Wert
Der Wert eines Ausdrucks wird als r-Wert bezeichnet. Der in einer einzelnen Variablen enthaltene Wert wird auch zu einem r-Wert, wenn er auf der rechten Seite des Zuweisungsoperators angezeigt wird. r-Werte können immer einer anderen Variablen zugewiesen werden.
l-Wert
Der Speicherort (Adresse), an dem ein Ausdruck gespeichert ist, wird als l-Wert dieses Ausdrucks bezeichnet. Es wird immer auf der linken Seite eines Zuweisungsoperators angezeigt.
Zum Beispiel:
day = 1;
week = day * 7;
month = 1;
year = month * 12;
Aus diesem Beispiel verstehen wir, dass konstante Werte wie 1, 7, 12 und Variablen wie Tag, Woche, Monat und Jahr alle r-Werte haben. Nur Variablen haben l-Werte, da sie auch den ihnen zugewiesenen Speicherort darstellen.
Zum Beispiel:
7 = x + y;
ist ein l-Wert-Fehler, da die Konstante 7 keinen Speicherplatz darstellt.
Formale Parameter
Variablen, die die von der Aufruferprozedur übergebenen Informationen übernehmen, werden als formale Parameter bezeichnet. Diese Variablen werden in der Definition der aufgerufenen Funktion deklariert.
Aktuelle Parameter
Variablen, deren Werte oder Adressen an die aufgerufene Prozedur übergeben werden, werden als Aktualparameter bezeichnet. Diese Variablen werden im Funktionsaufruf als Argumente angegeben.
Example:
fun_one()
{
int actual_parameter = 10;
call fun_two(int actual_parameter);
}
fun_two(int formal_parameter)
{
print formal_parameter;
}
Formale Parameter enthalten die Informationen des tatsächlichen Parameters, abhängig von der verwendeten Parameterübergabetechnik. Es kann ein Wert oder eine Adresse sein.
Wert übergeben
Beim Pass-by-Value-Mechanismus übergibt die aufrufende Prozedur den r-Wert der tatsächlichen Parameter, und der Compiler fügt diesen in den Aktivierungsdatensatz der aufgerufenen Prozedur ein. Formale Parameter enthalten dann die Werte, die von der aufrufenden Prozedur übergeben wurden. Wenn die von den formalen Parametern gehaltenen Werte geändert werden, sollte dies keine Auswirkungen auf die tatsächlichen Parameter haben.
Referenz übergeben
Im Pass-by-Referenzmechanismus wird der l-Wert des Aktualparameters in den Aktivierungsdatensatz der aufgerufenen Prozedur kopiert. Auf diese Weise hat die aufgerufene Prozedur nun die Adresse (Speicherort) des tatsächlichen Parameters und der formale Parameter bezieht sich auf denselben Speicherort. Wenn daher der Wert, auf den der formale Parameter zeigt, geändert wird, sollte die Auswirkung auf den tatsächlichen Parameter gesehen werden, da sie auch auf denselben Wert zeigen sollten.
Pass by Copy-Restore
Dieser Parameterübergabemechanismus funktioniert ähnlich wie "Referenzübergabe", außer dass die Änderungen an den tatsächlichen Parametern vorgenommen werden, wenn die aufgerufene Prozedur endet. Beim Funktionsaufruf werden die Werte der Istparameter in den Aktivierungsdatensatz der aufgerufenen Prozedur kopiert. Wenn formale Parameter manipuliert werden, haben sie keine Echtzeitwirkung auf die tatsächlichen Parameter (wenn l-Werte übergeben werden), aber wenn die aufgerufene Prozedur endet, werden die l-Werte der formalen Parameter in die l-Werte der tatsächlichen Parameter kopiert.
Example:
int y;
calling_procedure()
{
y = 10;
copy_restore(y); //l-value of y is passed
printf y; //prints 99
}
copy_restore(int x)
{
x = 99; // y still has value 10 (unaffected)
y = 0; // y is now 0
}
Wenn diese Funktion endet, wird der l-Wert des formalen Parameters x in den tatsächlichen Parameter y kopiert. Selbst wenn der Wert von y vor dem Ende der Prozedur geändert wird, wird der l-Wert von x auf den l-Wert von y kopiert, sodass er sich wie ein Referenzaufruf verhält.
Mit Namen übergeben
Sprachen wie Algol bieten eine neue Art von Parameterübergabemechanismus, der wie ein Präprozessor in C-Sprache funktioniert. Beim Pass-by-Name-Mechanismus wird der Name der aufgerufenen Prozedur durch ihren tatsächlichen Hauptteil ersetzt. Durch die Namensübergabe werden die Argumentausdrücke in einem Prozeduraufruf in Textform durch die entsprechenden Parameter im Hauptteil der Prozedur ersetzt, sodass sie nun mit tatsächlichen Parametern arbeiten können, ähnlich wie bei der Referenzübergabe.