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 der Programmierer nicht explizit im Code bereits festgelegt hat. Somit kann ein Programmierer das Runtime-System auch nicht direkt ansprechen oder steuern, sondern nur darauf vertrauen, 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 eines Runtime-Systems 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 die Definition gepflegt, dass alles, was nebst dem von dem Programmierer geschriebenen Code 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 versteht man Code, den der Compiler implizit (ohne Zutun des Programmierers) 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 Codes 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 dieserr 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 Parametern aufgerufen werden müssen. Als Programmierer schreibt man einen solchen Aufruf einfach in den Source-Code, der Compiler und der Linker jedoch müssen für solche Aufrufe impliziten Code erstellen, welcher exakt die gewünschte Parameterübergabe und den Sprung an die richtige Adresse ausführt.

In C++ wird zudem die Ermittlung von Typinformationen zu Laufzeit (das sogenannte RTTI: RunTime Type Information) unterstützt. Hierbei wird zu jedem Objekt, das mittels einer Klasse mit virtuellen Methoden deklariert wurde, implizit ein zusätzlicher Typ-Indikator gespeichert (Normalerweise als ein Integer-Wert). Dadurch ist es möglich, den Typ eines beliebigen Symbols dynamisch zu ermitteln, wie er bei der Erstellung des Symbols ursprünglich deklariert wurde. Dies wird zum einen für den dynamic_cast<>-Operator, wie auch für den typeid-Operator benötigt. Anhand dieser Information wird in C++ das Exception-Handling mit polymorphen Klassen erst möglich.

Ebenfalls in die Kategorie der impliziten Codes gehört der in C++ unterstützte Polymorphismus, allerdings benötigt dieser kein RTTI. Bei einem Aufruf eines polymorphen Objektes wird implizit der dynamische Typ des Objektes benutzt, um Felder und Methoden des Objektes anzusprechen. Dies geschieht mittels einer geschickten Aufgliederung der Feld- und 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 kann ein Programmierer während des Schreibens des Programmes schlicht nicht wissen. Beispielsweise weiss er nicht, auf was für einem Computer das Programm möglicherweise laufen wird, welcher und wieviel Speicher ihm zur Verfügung steht 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 der Programmierer oder 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. Ein Programmierer hat die Möglichkeit, diese Signalverarbeitung zu steuern und zu nutzen. Damit diese asynchrone und somit unvorhersehbare Kommunikation mit dem Betriebssystem möglich wird, gibt es die entsprechenden Implementation 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 eines Programmierers viel stärker beeinflussen. Es sei bemerkt, dass von einigen Programmierern 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 der Programmierer somit nach wie vor die volle Kontrolle über das Programm hat, genauso wie man es von einer Befehls-Sprache erwartet.