Einige der Ideen kennen wir bereits aus den vorhergegangenen Kapiteln. Beispiele sind dafür Dateibegriff und Standardverbindungen.
In diesem Kapitel wollen wir den Aufbau des Hauptprogramms und seine Einbettung in die hoffentlich vorhandene Umgebung besprechen. Der Standard spricht dabei von "hosted environment", also einer Umgebung für "C" auf einer Gastmaschine. Das Gegenstück dazu ist die "freestanding environment die Umgebung ohne eine Gastmaschine, die viele Dienstleistungen nicht kennt. Entwickeln Sie mit "C" Programme, die später in die ROMs einer Steckkarte geschrieben werden, dann hat das Programm zumeist kein Betriebssystem, das Dienstleistungen erbringen kann. Wir wollen hier nur Programme besprechen, die unter einem Betriebssystem ablaufen.
Der Startpunkt eines "C"-Programms ist die Funktion mit dem Namen main(). Funktionen werden aufgerufen. Und wer ruft main() auf? Zu main() wird immer ein Standard-Startmodul hinzugebunden, das die notwendige Umgebung aufbaut und schließlich main() ruft. Nach der Rückkehr aus main() kümmert sich das Startmodul dann noch um Aufräumarbeiten. Dieses Modul wird vom Kommandointerpreter, der jeweiligen Shell, gerufen.
Die Funktion main() wird vom Compiler als Ausnahmefall betrachtet. Um beiden möglichen Umgebungen, mit oder ohne Betriebssystem, gerecht zu werden, prüft der Compiler die Schnittstelle von main() nicht. Will man daher main() ohne Parameter verwenden, dann wird man zur besseren Lesbarkeit ein void anstelle der Parameter schreiben. Läßt man die Parameterschnittstelle ganz leer, dann hat man alten C-Stil gewählt. Die Deklarationen sind:
int main (void); /* nach ANSI-Standard */
main(); /* nach dem Buch von K&R (lieber
nicht mehr verwenden)*/
Wir wollen hier die ANSI-Version mit der Definition der Parameter in der Schnittstelle verwenden. Die ältere Version wurde im Buch von Kernighan und Ritchie beschrieben.
Wurden beim Aufruf des Programms mit "<<","| " oder ">>" Umlenkungen der Standardverbindungen vorgenommen, dann müssen diese Angaben Vorrang vor den Standardgeräten Tastatur und Bildschirm haben. Die Winkel kann man als Befehle an die Aufrufumgebung auffassen, diese Umlenkung vorzunehmen.
Bild 3-1: Übergabe der Aufrufparameter
Weiter wird ein Wortzähler mitgeführt, der die Anzahl der kopierten Worte enthält. Gleichzeitig wird aber auch ein internes Feld aufgebaut, in das man die Startadressen aller kopierten Worte einträgt. Die Angaben zur E/A-Umlenkung gehören nicht dazu, da sie als Kommandos an die Aufrufumgebung entfernt werden. Das Feld enthält als letzten Eintrag immer eine Adresse mit dem Wert NULL.
Einen Sonderfall stellt die Verwendung von Metasymbolen innerhalb der Dateinamen dar. Ein anderer Ausdruck für Metasymbole ist auch wildcards oder Blankozeichen. Auch Jokerzeichen habe ich schon als Bezeichnung gefunden. Gemeint sind spezielle Zeichen, die eine Sonderbedeutung haben und für andere normale Zeichen stehen können. Die bekanntesten sind das "*" und das "?".
Ein "*" kann für eine beliebige Anzahl von Zeichen stehen, ein "?" für genau eines. Das Kommando "ls -l a*" (entspricht: dir a*.*) zeigt daher alle Dateien an, die mit "a" beginnen.
Die Frage ist nur, wer das "*" auswertet. Unter UNIX ist das eine Aufgabe der Shell. Sie generiert alle in Frage kommenden Dateinamen und übergibt sie an das Programm. Ein Programm wird (im Normalfall) nicht erfahren, daß die Dateinamen mit Hilfe eines "*" zustande kamen. Falls jedoch kein Dateiname generiert werden konnte, wird der Suchtext an das Programm weitergereicht.
Beim Aufruf werden nun der Wortzähler und das Feld mit den Startadressen der einzelnen Worte als Parameter an main() übergeben. Schreibt man main() korrekt mit den beiden Parametern, dann kann main() folgendermaßen deklariert (und entsprechend definiert) werden.
int main ( int argc, char *argv[] ); oder:
int main ( int argc, char **argv );
Ältere Compiler, die noch nach K&R arbeiten, erwarten bei allen Funktionen die Definition der Parameter zwischen der Parameterliste und dem Beginn des Funktionsblocks.
int main (argc, argv) /* sehr veraltet */
int argc;
char *argv[];
{ .... }
ANSI-C-Compiler verstehen beide Versionen, wenn auch die K&R-Version veraltet und nur noch für ältere Programme vorhanden ist.
Der erste Parameter enthält die Anzahl der Worte der Kommandozeile. Da mindestens der Programmname in der Kommandozeile angegeben werden muß, ist die Anzahl minimal eins. Der zweite Parameter beschreibt das Feld aus Zeigern auf Texte, die wie gewohnt in "C" als Zeiger auf char dargestellt werden.
Auf die übergebenen Argumente kann man mit Hilfe der beiden Parameter zugreifen. Dazu gibt es zwei Methoden: die eine wertet den Zähler aus und die zweite sucht den letzten Eintrag in der übergebenen Tabelle, der NULL ist.
Bild 3-2: Ausgabe der Parameter (mit argc)
Es gibt zwei Schreibweisen für den zweiten Parameter, die aber das gleiche bedeuten. Die erste Schreibweise gibt an, daß ein Feld aus Zeiger auf char übergeben wird, und die zweite berücksichtigt, daß Felder immer mit ihrer Startadresse übergeben werden. Also haben wir hier einen Zeiger auf das erste Feldelement, das ja wieder ein Zeiger auf char ist. Daher kommt die Notation: Zeiger auf Zeiger auf char.
Bild 3-3: Ausgabe der Parameter (mit argv)
Die Hauptfunktion muß nun die Kommandozeile interpretieren. Betrachten wir dazu den Aufbau einer Kommandozeile unter UNIX, den Aufbau unter DOS und danach eine Anpassung der DOS-Umgebung.
Eine Kommandozeile unter UNIX besteht aus den Komponenten:
Bild 3-4: Einfacher Scanner für Optionen
programm -opt -g datei1 datei2 verzeichnis >> ausgabe
Die Hauptfunktion muß nun die übergebenen Parameter untersuchen, ob und wieviele Optionen gesetzt wurden, und kann dann den Rest der Parameter als Dateinamen auffassen.
Ein weiterer Gesichtspunkt sind Randbedingungen. Was soll ein Programm tun, wenn zu wenige oder gar keine Dateien im Aufruf angegeben werden? Die Lösung heißt dann: umschalten auf die Standardverbindungen.
Die Kommandozeile für DOS schaut etwas anders aus. Hier stehen die Optionen zumeist nach dem oder den Dateinamen und werden durch einen normalen Schrägstrich eingeleitet. Dieses Verfahren funktioniert am besten, wenn die Anzahl der möglichen Dateien bei jedem Aufruf genau festliegt.
programm datei1 datei2 /opt /g >> ausgabe
Bei DOS wird das "*" nicht von der Umgebung ausgewertet, sondern muß von jedem Programm selbst behandelt werden. Ob das sinnvoll ist?
Es gibt bei den einigen DOS-Compilern ein spezielles, zweites Startmodul, das zum normalen Programm hinzugebunden werden kann. Es übernimmt die mühsame Expansion der Metasymbole. Bei TurboC heißt das Modul wildargs.obj.
Bild 3-5: Parameter mit Wortangabe
Es ist eine häufige Praxis, daß man Optionsvariablen als globale Variablen setzt. Jede Funktion kann dann darauf zugreifen und eventuell gesetzte Optionen berücksichtigen. Die Variablen werden dabei stets mit einem Standardwert vorbesetzt und bei gültiger Option geändert.
Dieses Verfahren ist ziemlich einfach zu programmieren, erlaubt aber nur das Setzen von mehreren Schaltern. Will man als Optionen auch Worte angeben können, um z. B. einen bestimmten Treiber angeben zu können, müssen wir den Aufbau der Kommandozeile festlegen. Wenn alle Optionen vor den Dateinamen stehen, dann brauchen wir nur noch einen Zähler, der den Beginn der Dateinamen angibt.
Im Bild 5 wird der Scanner leicht geändert. Nun wird aber erwartet, daß die Optionen der Kommandozeile nur am Anfang vor den Dateinamen stehen. Folgerichtig bricht der Suchvorgang auch beim ersten Auftreten einer Nichtoption ab. Daher wurde aus der for-Schleife nun eine do-while-Schleife. Eine häufig anzutreffende Besonderheit wurde mit der f-Option angedeutet.
In vielen Programmen will man angeben können, welches Gerät oder welche Datei gerade benutzt werden soll. Die f-Option erwartet im nächsten Parameter den Namen eines Gerätes oder einer Datei und speichert ihn in einem Feld ab. Die Funktion strcpy() kopiert einen String einschließlich der abschließenden "\0". Auf eine Fehlerüberprüfung wurde beim Kopieren verzichtet, um nur das Wesentliche zu zeigen.
Nach dem Ablauf der Suchschleife steht uns in der Variablen dateibeginn der erste Index für argv zur Verfügung, der einen Dateinamen enthält. Alle weiteren Worte sind daher Dateinamen.
In einigen Compilern gibt es auch eine eigene Funktion zum Absuchen der Kommandozeile: getopt(). Sie ist wie folgt deklariert:
int getopt (int argc, char *argv(), char *Optionen);
Da diese Funktion (leider) nicht zum ANSI-C-Standard gehört, sollten Sie bei Bedarf einmal im Handbuch des Compilers nachsehen, wie sie angewendet wird. Neben der Funktion sind noch globale Variablen notwendig.
Bild 3-6: Aufbau der Umgebungsvariablen
Die Umgebungsvariablen sind Texte mit einem bestimmten Aufbau. Ein Name (oft in Großbuchstaben) wird gefolgt von einem "="-Zeichen und einem Text. Die wohl bekannteste Umgebungsvariable ist PATH, die Angabe der Pfade, unter denen Programme zu finden sind.
Auf diese Texte kann mann genauso wie auf die einzelnen Worte der Parameterzeile zugreifen. Viele Programme sehen in Umgebungsvariablen nach, wo ihre Verzeichnisse liegen. Der Kommandointerprter unter DOS erwartet die Variable "COMSPEC", um lesen zu können, wo er selber auf der Platte liegt.
In ANSI-C wurde statt eines dritten Parameters eine globale Variable "environ" in "stdlib.h" deklariert. Die Verwendung entspricht dem dritten Parameter, wie im Beispiel zu sehen ist.
Bild 3-7: Übergabe der Umgebungsvariablen an "main()"
#include <stdlib.h>
char *getenv (const char * name);
int putenv(const char *text); /* nicht ANSI,
aber UNIX */
getenv() erhält einen Variablennamen als Parameter und liefert einen Zeiger auf den dazugehörenden Text zurück. Der Name der Variablen muß korrekt angegeben werden. Wenn die Umgebungsvariable mit Großbuchstaben definiert wurde, oder wenn der Kommandointerpreter automatisch eine Großschreibung erzwingt (wie bei DOS), dann muß auch im Suchtext von getenv() der Name groß geschrieben werden.
Im Fehlerfall, wenn der gesuchte Name nicht gefunden wurde, liefert getenv() einen NULL-Zeiger.
Bild 3-8: Auslesen einer Umgebungsvariablen
Das Gegenstück dazu ist putenv(). Hier bilden wir innerhalb des Programmes einen Text und tragen ihn in der Umgebung ein. Allerdings kann damit nur die Umgebung des eigenen Programms verändert werden. Der übergebene Text wird nicht kopiert. Daher muß der Text in einer globalen oder statischen Variable stehen und unverändert stehen bleiben. Diese Funktion wird im Standard nicht beschrieben, ist aber unter DOS und UNIX oft vorhanden.
Der Rückgabewert ist "0" bei Erfolg und "-1" bei Mißerfolg. Der normale Grund für ein Mißlingen ist fehlender Speicherplatz im Umgebungsbereich.
Bild 3-9: Setzen einer Umgebungsvariablen
Bild 3-10: Rückgabe des Endestatus
Am Ende des Programms gibt man einen Endestatus zurück. Im Kommandointerpreter steht der Endestatus in einer speziellen Variablen zur Verfügung. Unter DOS gelangt der Wert nach ERRORLEVEL und unter UNIX ist dies "$?". In jedem Fall kann der Wert innerhalb von Batch-Dateien, die unter UNIX Scripten heißen, zur weiteren Ablaufsteuerung benutzt werden.
Nach return steht der Endestatus in Form einer int-Zahl. ANSI-C hat für die Rückgabe in der stdlib.h nur zwei symbolische Konstanten definiert: EXIT_SUCCESS und EXIT_FAILURE. Um kompatibel zu anderen C-Programmen zu sein, gilt auch die Zahl "0" als Kennung für den Erfolg.
Bild 3-11: Holen einer Ja/Nein-Entscheidung
Es werden Ihnen immer wieder Beispiele begegnen, in denen auf die richtige Rückgabe des Programmstatus kein Wert gelegt wird. Sie sollten trotzdem stets darauf achten. Ohne sinnvollen Rückgabestatus kann das Programm innerhalb von Scripten (Batch-Dateien) nur sehr eingeschränkt benutzt werden.
Um auch an beliebigen Stellen ein Programm abbrechen zu können, gibt es die Funktion exit(), der man ebenfalls einen Endestatus mitgeben kann. Diese Funktion kann an allen Stellen, also auch innerhalb einer tiefen Verschachtelung, gerufen werden.
Bild 3-12: Auswertung des Rückgabestatus unter DOS
Als Anwendungsbeispiel soll ein Programm eine Ja/Nein-Entscheidung einlesen und den Rückgabewert in Abhängigkeit von der Eingabe setzen. Das Programm erwartet zwei Parameter. Der erste Parameter soll alle Buchstaben beinhalten, die einen Ja-Fall einleiten können, der zweite die Buchstaben, die einen Nein-Fall einleiten.
Das Programm wartet auf eine Eingabe mit getchar(). Diese Funktion liest eine ganze Zeile und wartet auf einen Zeilenvorschub. damit kann der Anwender eventuelle Fehleingaben korrigieren. Der erste Buchstab wird dann mit den beiden Parametern verglichen, die als Texte über argv übergeben werden.
Die Funktion strchr() sucht im übergebenen Text nach dem ebenfalls übergebenen Zeichen. Sie liefert NULL, falls das Zeichen nicht im Text gefunden wird. Weitere beispiele zu Texten finden Sie auch im Kapitel über Texte. Das Programm hat nun vier Möglichkeiten der Rückgabe:
9 Parameterfehler
8 Zeichen nicht gefunden
1 Ja-Wert gefunden
0 Nein Wert gefunden
Die Rückgabewerte kann man in einer Batch-Datei (script) auswerten. Dazu verwendet man die "if"-Abfrage zusammen mit ERRORLEVEL.
Die Auswertung geschieht mit einer Abfrage nach größer oder gleich. daher stehen die Abfragen nach höheren Werten immer zuerst.
Die minimale Anzahl möglicher Registrierungen ist 32. Eine Funktion kann auch mehrfach eingetragen werden, so daß sie auch mehrfach ausgeführt wird. Der Aufruf erfolgt in der umgekehrten Reihenfolge wie der Eintrag. Was zuletzt eingetragen wurde, wird zuerst ausgeführt. Die Registrierung geschieht mit atexit().
int atexit ( void (*funktion) (void) );
Der Parameter schaut komplizierter aus, als er tatsächlich ist. funktion ist eine Funktionsadresse. Zusammen mit dem "*" steht in der inneren Klammer daher ein Funktionsname. Die Funktion, deren Adresse hier angegeben wird, erhält keine Parameter und liefert nichts zurück. Im Aufruf kann man für den Parameter einfach den Namen einer entsprechenden Funktion verwenden. Der Funktionsname allein ohne "()" steht in "C" für die Startadresse der Funktion.
Die Rückgabe ist "0" bei Erfolg, nicht "0" bei Mißerfolg.
Bild 3-13: Aufruf registrierter Funktionen am Programmende
Es gibt mehrere Auslöser, die zum Programmabbruch führen können, wie Hardwarefehler, Grenzwertüberschreitungen bei der Arithmetik oder Benutzereingaben. Daher gibt es auch mehrere mögliche Signale. Jedes Signal wird durch eine Nummer beschrieben. Die Tasten-Kombination Ctl-C nennt man Unterbrechung ("Programm-Interrupt"). Dafür ist traditionell die Signalnummer 2 reserviert. Ein Signal kann man auch als eine vordefinierte Botschaft betrachten, deren Bedeutung das Programm an Hand einer Signalnummer erkennen kann. (Der Begriff "interrupt" hat hier eine andere Bedeutung als in der Hardware.)
Bild 3-14: Signaltabelle in der Umgebung
Signale treffen bei einem Programm zu einem Zeitpunkt ein, der nichts mit dem normalen Ablauf zu tun hat. Woher soll ein Programm wissen, wann Sie die Tasten CTL-C (oder die entsprechenden) drücken, um ein Programm abzubrechen? Man sagt auch, die Signale sind asynchron zum Programmablauf.
Jedes Programm hat in seiner Umgebung eine Tabelle mit Adressen von Funktionen. (Im Bild wurde die Tabelle der Einfachheit halber mit typlosen Adressen aufgebaut.) Wird ein Signal erzeugt, z. B. durch das Drücken einer speziellen Tastenkombination, dann wählt das Betriebssystem aus dem Eintrag, der der Signalnummer entspricht, die Funktion aus und ruft sie auf. Normalerweise wurden beim Start alle Tabelleneinträge mit der Adresse einer Funktion vorbelegt, die das Programm abbricht.
Bild 3-15: Abfangen des Ctl-C Signales
Im Beispiel haben wir mit Hilfe der Funktion signal() einen der Tabelleneinträge mit der Startadresse einer Bearbeitungsfunktion für Signale überschrieben. Der Eintrag wurde durch die symbolische Konstante "SIGINT" beschrieben. Jede Signalnummer hat einen Namen, der in der Informationsdatei signal.h beschrieben wird.
Die Funktion signal() ist in signal.h deklariert. Man kann die etwas komplexe Deklaration vereinfachen, wenn man sie in zwei Schritten vornimmt. Zuerst legt man mit typedef den frei gewählten Typnamen "sfun" fest. Hier ist "sfun" ein Zeiger auf eine Funktion, die einen int-Parameter erhält und nichts zurückliefert. Dies ist der Typ von Funktionen, die als Signalhandler verwendet werden können. Die Registrierungsfunktion signal() erwartet nun eine Signalnummer (d. h. einen Index) und die Adresse eines Signalhandlers.
#include <signal.h>
typedef void (*sfun)(int);
sfun signal (int sig, sfun funktion); /*
oder auch in einer Zeile.. */
void (*signal (int sig, void (*func) (int)))
(int); /*ANSI */
int raise (int sig); /* Auslösen
eines Signales */
Als Ergebnis wird die als Parameter übergebene Funktionsadresse zurückgeliefert. Im Fehlerfall meldet signal() die Konstante SIG_ERR und einen Fehlercode in errno.
Der Standard verwendet zur Deklaration der Funktion signal() eine nicht ganz einfache, verschachtelte Schreibweise, die der Vollständigkeit halber mit angegeben wurde..
Es gibt dabei drei Möglichkeiten für den Signalhandler:
Diesen Abbruch liefert Ctl-C. Der Name des damit erzeugten Signals ist SIGINT (Benutzer-Interrupt). Sollte Ctl-C nicht abbrechen, gibt es noch die Tasten Ctl-Untr oder, bei UNIX, die Taste, die beim stty-Kommando als Interrupt-Taste angezeigt wird.
Das SIGINT-Signal hat per definitionem die Nummer 2. Das Betriebssystem wird daher beim Index 2 in der Signaltabelle nachschauen, welcher Wert dort hinterlegt ist.
In unserem Fall haben wir mit der Funktion signal() eine eigene Funktion registrieren lassen, die daher beim Eintreffen des Signals aufgerufen wird. Am Bildschirm sollte die Meldung erscheinen, daß ein Signal mit der Nummer 2 erkannt worden ist. Die Signalfunktionen haben einen Parameter, der die Nummer beim Aufruf erhält.
Drücken Sie nun erneut Ctl-C, dann bricht das Programm wie gewohnt ab. Ein Eintrag in der Signaltabelle gilt nur für einen Aufruf. Das Betriebssystem trägt von sich aus immer wieder die Standardfunktion ein, die zum Abbruch führt. Will man alle Signale wiederholt abfangen, muß man innerhalb der Signalfunktion diese Signalfunktion wieder mit signal() registrieren.
Abhängig von dem verwendeten Betriebssystem und der eingesetzten Hardware, kann es noch eine ganze Reihe von Signalen geben. Insbesondere UNIX verwendet noch eine ganze Anzahl.
Signale sind ein erster Hinweis darauf, daß man mit einer Programmierung mit Funktionen nicht alle Probleme lösen kann. Das Gegenstück zu einem Denken in einer Funktionshierarchie ist die Vorstellung, daß selbständige Einheiten sich Botschaften senden. Dies ist auch die Grundidee der OOP, der objektorientierten Programmierung, und der graphischen Benutzeroberflächen wie X-Windows oder MS-Windows.
In ANSI-C wurde für solche Anwendungsfälle die Bibliotheksfunktion system() definiert.
#include <stdlib.h>
int system (char * kommando);
An die Funktion wird eine Kommandozeile übergeben, die an einen zusätzlich aufgerufenen Kommandointerpreter weitergereicht wird.
Bild 3-16: Aufruf eines Systemkommandos (DOS)
Der system()-Aufruf startet einen Kommandointerpreter (unter DOS zumeist command.com, unter UNIX eine Shell) und übergibt die Kommandozeile. Der Rückgabewert von system() ist abhängig von der Implementierung. Entweder wird der Startvorgang des Kommandointerpreters bewertet oder der Endestatus des aufgerufenen Programmes zurückgegeben. Der hier verwendete Compiler TurboC++ 3.0 liefert im Fehlerfall einen Wert ungleich "0".
Im Beispiel (Bild 16) wird daher geprüft, ob der Aufruf den Wert "0" geliefert hat oder nicht.
Mit Hilfe der Systemkommandos kann man ganze Anweisungssequenzen aufbauen. Das nächste Beispiel (Bild 17) zeigt das Sortieren einer Datei in eine Hilfsdatei. Die Originaldatei soll gelöscht und die sortierte Datei soll durch Umbenennen den Platz der Originaldatei einnehmen. Das Programm gilt für DOS.
Die Aufgabe kann man durch drei Systemkommandos lösen. Zuerst benötigen wir sort, dann del und schließlich ren. Damit im Fall eines vorzeitigen Abbruchs den Datenbeständen nichts passieren kann, bricht das Programm jeweils im Fehlerfalle ab.
Bild 3-17: Sortieren einer Datei mit Hilfe von Systemaufrufen
Das dritte und letzte Beispiel zum Funktionsaufruf system() verwendet einen Spezialfall. Anstelle des Kommandozeilentextes kann auch einen NULL-Zeiger übergeben. Dies prüft, ob ein Kommandointerpreter zur Verfügung steht.
Bild 3-18: Test auf Kommandoprozessor
Der Rückgabewert ist nur dann nicht "0", wenn ein Kommandoprozessor zur Verfügung steht.
Der Speicher muß dabei so groß sein, daß beide Programme gleichzeitig darin Platz haben. Das ist bei den meisten Betriebssystemen kein Problem. Die Ausnahme ist DOS. Zuerst schreiben wir ein kleines Programm, das dann später aus einem zweiten heraus aufgerufen werden soll.
Bild 3-19: Testprogramm zum Aufrufen
In einem anderen Programm kann man nun mit Hilfe der exec..()-Funktion unser Testpreogramm aufgerufen werden. Die exec..()-Funktionen gibt es mit den Kennbuchstaben "l" für Argumentliste, "e" für Environment (Umgebung) angegeben, "p" für Pfadsuche über PATH und "v" für variable Argumentanzahl.
Im ersten Beispiel verwenden wir execl() als einfachste Möglichkeit. Als Argumente gibt man zuerst die Programmdatei mit ihrem absoluten Pfad an. Unter DOS darf der Pfad entfallen, wenn die gerufene Programmdatei im momentanen Verzeichnis steht. In den restlichen Parametern gibt man die Argumente für das gewünschte Programm an. Beachten Sie hier, daß der Parameter argv[0] explizit gesetzt werden kann. Das hat unter DOS keine Bedeutung, erlaubt aber unter UNIX den Namen der Programmdatei von dem Namen unterschiedlich zu machen, unter dem das Programm sich selbst sieht. Das benutzt man z. B., um den ersten Aufruf des Kommandointerpreters von weiteren zu unterscheiden.
int execl(char *Datei,char *arg0,char * arg1,.../*,NULL*/);
int execle(char *D,char *a0,char *a1,.../*,NULL,char
*e[]*/);
int execlp(char *Datei, char *arg0,char *arg1,.../*,NULL*/);
int execvpe (char *pfad, char *argv[],char
*env[]);
usw.
Vielleicht wundern Sie sich über die Kommentare in der obigen Aufstellung. In ANSI-C kann eine Deklaration mit dem Auslassungszeichen "..." enden, danach dürfen jedoch keine weiteren Parameter angegeben werden. Will man trotzdem aus Dokumentationsgründen die letzten Parameter spezifizieren, geht dies nur als Kommentar.
Bild 3-20: Aufruf eines anderen Programmes
Welche Versionen auf einem bestimmten Rechner vorhanden sind, muß man der Dokumentation entnehmen. Wir wollen noch eine Grundform betrachten.
Die zweite Grundform spezifiziert für ein aufgerufenes Programm nicht nur die Parameter, sondern auch dessen Umgebungsvariable. Um diesen Aufruf testen zu können, verwenden wir ein weiteres Programm, das nur seine Umgebungsvariablen ausgibt. Dieses Programm rufen wir dann mit der geeigneten exec..()-Funktion auf.
Bild 3-21: Testprogramm zur Ausgabe der Umgebung
Das Testprogramm wird von einem anderen Programm gestartet, das die Umgebung explizit setzt. Lassen wir das Startprogramm laufen, startet es die Ausgabe der Umgebungsvariablen. Das Ergebnis sieht dann wie folgt aus.
Bild 3-22: Ausgabe der Umgebungsvariablen (von Bild 23)
Aufrufe von anderen Programmen kommen hauptsächlich in Umgebungen vor, die Multitasking unterstützen. Unter DOS wäre eine Verwendung in einem Menüsystem denkbar.
Bild 3-23: Programmaufruf mit spezieller Umgebung
Neben den exec..()-Funktionen werden häufig auch spawn..()-Funktionen oder fork() unterstützt. Alle diese Funktionen dienen dem Start anderer Programme und Prozesse, sind nicht im ANSI-C-Standard erwähnt und sind auch nur unter Multitasking-Systemen sinnvoll.
Im Gegensatz zu den exec..()-Funktionen können fork() und spawn..() Prozesse starten, ohne das Startprogramm zu beenden.