Runtime-System

Ein Runtime-System, oder zu Deutsch Laufzeit-System, hat die Aufgabe, das schlussendliche Programm zur Laufzeit, also während der eigentlichen Ausführung, zu unterstützen. Ein Runtime-System regelt grundsätzlich all das, was nicht explizit im Sourcecode bereits festgelegt wurde. Somit kann das Runtime-System auch nicht direkt angesprochen oder gesteuert werden, sondern es muss darauf vertraut werden, dass es das Programm in angemessener Weise unterstützt.

Details

Grundsätzlich gilt: Das Runtime-System unterstützt das Programm zur Laufzeit. Doch wenn es um eine genaue Definition des Wortes Runtime-System geht, scheiden sich die Geister. Der Autor hatte Mühe, die hier dargestellten Inhalte auf einen Nenner zu bringen. Einige Quellen behaupten, dass C gar keine und C++ nur eine zusammengeschusterte und kaum verwendbare Laufzeitunterstützung hat. Hier jedoch wird (nach Gutdünken des Autors) die Ansicht gepflegt, dass alles, was nebst dem geschriebenen Sourcecode zusätzlichen Aufwand an Zeit und Speicher benötigt, zum Runtime-System gehört. Die Grenzen zwischen Sprach-Unterstützung (jeglicher Zusatzaufwand, der zur Umsetzung des Codes notwendig ist) und Laufzeit-Unterstützung sind jedoch nur unscharf. Die Funktionalität eines Runtime-Systemes wird hier grob unterteilt in implizite Codes und Meta-Befehle.

Implizite Codes

Unter impliziten Codes werden Bits und Bytes verstanden, die der Compiler implizit (ohne dass hierfür zusätzlicher Sourcecode geschrieben werden muss) einfügt oder so anordnet, dass das Programm lauffähig wird. Grundsätzlich werden bei C und C++ die Anweisungen, die im Source-Code geschrieben werden, direkt in Assemblercode übersetzt. Dies alleine genügt jedoch nicht, um ein Programm zum Laufen zu bringen. Da Source-Code meistens in mehrere Teile gegliedert ist, ergeben die einzelnen Codefragmente keinen Sinn und müssen mittels zusätzlicher Maschinencodes und geschickter Anordnung lauffähig gemacht werden. In manchen Quellen werden implizite Codes nicht als Laufzeit-Unterstützung angesehen, da diese Codes während der Kompilation bereits festgelegt werden können. Hier auf dieser Seite werden sie jedoch aufgeführt mit Fokus auf den unterstütztenden Charakter.

Die Anweisungen eines Programmes stehen normalerweise in mehreren Funktionen, welche an bestimmten Stellen mit den korrekten Argumenten aufgerufen werden müssen. Funktionsaufrufe werden einfach irgendwo im Source-Code hingeschrieben, der Compiler und der Linker jedoch müssen für solche Aufrufe impliziten Code erstellen, welcher exakt die gewünschte Argumenten-Übergabe und den Sprung an die richtige Adresse ausführt.

Ebenfalls in die Kategorie der impliziten Codes gehört der in C++ unterstützte Polymorphismus. Bei einem Aufruf eines polymorphen Objektes wird implizit der dynamische Typ des Objektes benutzt, um Methoden des referenzierten Objektes anzusprechen. Dies geschieht mittels einer geschickten Aufgliederung der Methodenreferenzen im gespeicherten Objekt. Diese Anordnung wird implizit von C++ vorgegeben.

Je nach Betrachtungsweise können weitere Beispiele für implizite Codes aufgelistet werden. Im Rahmen dieser C/C++ Referenz sei hier jedoch mit den obengenannten wichtigsten Vertretern diese Kategorie abgeschlossen.

Meta-Befehle

Gewisse Dinge sind während des Schreibens des Programmes schlicht nicht bekannt. Beispielsweise ist unbekannt, auf was für einem Computer das Programm möglicherweise laufen wird, welcher und wieviel Speicher zur Verfügung stehen oder was für Prozesse sonst noch laufen. Meta-Befehle stehen über dem Programm und kommunizieren mit dem Betriebssystem, was es ihnen erlaubt, dem Programmablauf übergeordnete Funktionalitäten anzubieten. Meta-Befehle definieren komplexen Code, oftmals versteckt hinter einer Funktion der Standardbibliotheken oder einem Operator zusammen mit impliziten globalen Variablen und einem Aufruf einer Betriebssystemprozedur. Die tatsächliche Ausprogrammierung jedes Meta-Befehls ist betriebssystem- und compilerabhängig.

Eine sehr wichtige Operation in C und C++ sind Speicher-Allokationen und -Deallokationen. In C gibt es hierfür die malloc- und free-Funktionen, in C++ die new- und delete-Operatoren. Auch wenn bei malloc und free explizit von Funktionen geredet wird, haben sie genauso wie new und delete schlussendlich die Aufgabe, das Betriebssystem um Speicher anzufragen oder diese Betriebssystem-Funktionalität selbst zu implementieren. Die Anfrage muss anhand Betriebssystem-spezifischer Methoden erfolgen, ohne dass das Programm etwas davon mitkriegt. Die Adresse des Speichers, welche durch die malloc- oder new-Operation zurückgegeben wird, kann vor der tatsächlichen Laufzeit nicht vorausgesagt werden.

Ein Betriebssystem muss dazu imstande sein, beispielsweise ein Programm notfalls abzubrechen, es schlafen zu legen, wieder aufzuwecken oder abgelaufene Countdouns mitzuteilen. Dies wird mittels sogenannten Signalen erreicht, welche den normalen Programmablauf augenblicklich unterbrechen und einen zu dem gesendeten Signal passenden Code ausführen. Diese Signalverarbeitung kann in C gesteuert und genutzt werden. Damit diese asynchrone und somit unvorhersehbare Kommunikation mit dem Betriebssystem möglich wird, gibt es die entsprechenden Implementationen der signal-Befehle, welche schlussendlich ebenfalls als Meta-Befehle zu verstehen sind.

In dieselbe Kategorie gehören die Eingabe- und Ausgabe-Funktionen für Dateien, Geräte, Sockets, Pipes, usw. Zwar sind sämtliche benötigten Funktionen in den Standardbibliotheken vorhanden, doch sind auch deren Ausprogrammierungen pro System und Compiler unterschiedlich.

Es sei angemerkt, dass nicht sämtliche Funktionen der Standardbibliotheken als Meta-Befehle zu betrachten sind. Wiederum könnten weitere Beispiele für Meta-Befehle aufgelistet werden, doch für diese C/C++ Referenz soll obengenanntes genügen.

Schlussbemerkung

Zwar ist das Runtime-System in C und C++ für viele Programme überlebenswichtig, doch für die alltägliche Programmierung spielt es eine untergeordnete Rolle, es ist einfach da und tut seinen Dienst. Andere Programmiersprachen haben teilweise viel weiter entwickelte Runtime-Systeme, welche vermehrt zu einem Hauptbestandteil der Programmierung werden und somit die Design-Entscheidungen beim Schreiben eines Programmes viel stärker beeinflussen. Es sei bemerkt, dass die Sprache C++ und insbesondere C sehr geschätzt wird, gerade deswegen, da das Laufzeitsystem nicht ständig reinfunkt, sich also das Mass an Unterstützung in Grenzen hält und während der Programmierung somit nach wie vor die volle Kontrolle beibehalten wird, genauso wie es von einer Befehls-Sprache erwartet wird.

Nächstes Kapitel: Memory, Heap, Stack, Loader