Praktische Einführung mit Virtualisierung
Stefan Bosse
Universität Koblenz - FB Informatik
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung ::
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Literatur
Jürgen Wolf, René Krooß
Grundkurs C
3., aktualisierte und überarbeitete Auflage 2020
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: C Compiler
Es gibt verschiedene Compiler. Folgende Auswahl sind bekannte und weniger bekannte C Compiler:
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: C Compiler
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: C Workflow und Toolchain
Der Workflow (und die Toolchain) ist i.A. nicht monolithisch, sondern besteht aus einer Vielzahl von einzelnen Schritten mit eigenen Programmen.
Typischer C Workflow: Präprozessor CPP → C Compiler CC → Assembler ASM → Linker LD
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: C Workflow
Makros
#define X 1#define PRINT2(a,b) \ printf("%d:%d",a,b)#define LINUX 1int x=X;
Konditionale 1
#ifdef LINUX XXX#endif#ifdef LINUX XXX#else YYY#endif
Konditionale 2
#if (X > 0) #define XSIGN 1#else #define XSIGN 0#endif#if (X > 0) unsigned int x;#else signed int x;#endif
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: C Workflow
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Der C-JS Transpiler
In diesem Kurs wird C mittels eines C-JavaScript Transpilers geübt un erprobt.
Der C-JS Transpiler besteht aus folgenden Komponenten:
Der generierte JS Code ist lesbar und kann eingesehen werden um ein tieferes Verständnis eines C Programms zu erhalten. Alles dreht sich um ein zentrales Speichermodell und Zugriff darauf.
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Der C-JS Transpiler
Aufbau des C-JS Transpiler mit optionalen inkrementellen Kompilieren
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Variablen und das Speichermodell
Die C Programmiersprache implementiert ein prozedurales und imperatives Programmiermodell mit zustandsbehafteten Variablen und zustandsänderndern Operationen inklusive Funktionen.
Eine Variable bezeichnet durch name befindet sich im Speicher Σ, formal ausgedrückt:
var(name)⇔{σ,δ,ϵ}var(name)⇒Block(σ,σ+δ)Block(a,b)⇒[ϵ]ba∈Σ
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Variablen und das Speichermodell
┌―――――――┐ ┌―――――――┐ │ x │ │ │ ├―――――――┤ ├―――――――┤ │ y │ │ z │ ├―――――――┤ ├―――――――┤ │ z │ │ │ int x,y,z;├―――――――┤ ├―――――――┤ │ │ │ x │ │ │ ├―――――――┤ │ │ │ │ │ │ ├―――――――┤ │ │ │ y │ └―――――――┘ └―――――――┘
Zwei verschiedene Speicherallokationen für die Variablen x,y,z: Linear fortlaufend versa verstreut
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Speichermodell C-JS Transpiler
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Speichermodell C-JS Transpiler
Speichermodell des CJS Transpilers, DS: Datensegment, CS: Code Funktion Index Tabelle, CSI: Inverse CS Tabelle
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Definition versa Deklaration
int x; extern int x;int foo(int x) { return x }; int foo(int x); extern int foo(int x);
(Links) Definition (Rechts) Deklaration
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Symbole in C
Ein Symbol ist durch einen Namen (lexikalische Folge aus Textzeichen) und einer operationalen Semantik gekennzeichnet.
Es gibt folgende Symbolklassen:
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Symbole in C
Bezeichner sind Namen für Objekte in einem Programm, die vom Programmierer festgelegt werden können, etwa Variablen, Funktionen usw. Für die Namen für gültige Bezeichner gelten folgende Regeln:
int osMinorVersion2=1;int main(int x) { .. };struct node { .. };
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Symbole in C
Auf Bezeichner, die mit zwei sequenziellen Unterstrichen oder einem Unterstrich, gefolgt von einem Großbuchstaben beginnen, sollte verzichtet werden, weil sie für C-Implementierungen reserviert sind.
Bezeichner wie __asdf
sind für gewöhnlich für Compiler-Zwecke, Bezeichner wie _Asdf
für Betriebssystem- und Bibliotheken gedacht.
Bei gcc kann die Liste von vordefinierten Bezeichnern (mit Werten) mittels echo | gcc -dM -E -
ausgegeben werden:
__UINT8_MAX__ 0xff__unix 1__INT_WIDTH__ 32
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Symbole in C
Reservierte Schlüsselwörter dürfen nicht als Bezeichner verwendet werden (auch nicht in Feld- und Attributenamen),
auto break case char const continue default do double else enum extern float for goto if int longregister return short signed sizeof unsigned void volatile while
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Datentypen
Wir unterscheiden folgende Datentypen in C:
Nicht nutzerdefinierbare Kerndatentypen (zunächst Skalarwerte):
long
,int
,short
)double
,float
)char
)*
)Benutzerdefinierte Aggregate
typedef
, struct
)t[]
,* t
)Datentypmodifizierer
signed
,unsigned
)long
,short
)Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Boolescher Datentyp
In C gibt es keinen Booleschen Datentyp. Aber dieser kann einfach mit einer Ganzzahl implementiert werden. Dazu verwendet man eine Enumeration.
enum bool_vals {false,true};typedef enum bool_vals bool;bool b1=false;
enum
die eigentliche Enumeration, mittels typedef
wird aus der Wertemenge bool
ein neuer Datentyp.Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Enumerationen
enum <ennumname> { C1, C2=<expressio>, ..};
Eine Eumeration führt konstante benannte Werte ein
#define C1 0#define C2 <expression>...
Alternative mit Präprozessor Makrodefinitionen
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Variabledefinition
<datentypmod> <datentyp> <name>;<datentypmod> <datentyp> <name> = <initval>;
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Begrenzer
Um einzelne Symbole voneinander zu trennen, werden sogenannte Begrenzer benötigt.
Begrenzer | Bedeutung |
---|---|
Semikolon (;) | Dient als Abschluss einer Anweisung. Jeder Ausdruck, der mit einem Semikolon endet, wird als Anweisung behandelt. Der Compiler weiß dann, dass hier das Ende der Anweisung ist, und fährt nach der Abarbeitung dieser Anweisung mit der nächsten fort. |
Komma (,) | Mit dem Komma trennen Sie für gewöhnlich gleichartige Elemente, z. B. können Sie mehrere Variablen für Ganzzahlen so definieren: int minSp, maxSp, startSp; |
geschweifte Klammern ({}) | Zwischen den geschweiften Klammern wird ein Anweisungsblock zusammengefasst. In diesem befinden sich alle Anweisungen (abgeschlossen mit einem Semikolon), die ausgeführt werden sollen. |
Gleichheitszeichen (=) | Das Gleichheitszeichen = steht in C für eine Zuweisung, z. B. in int maxSpieler = 500; |
Einfache Begrenzer in C
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Speicherbedarf
Eine Variable belegt immer einen Teil des Datenspeichers, auch bei Zeigervariablen (kommen noch).
Speicherbedarf mit sizeof ermitteln
Wenn die Größe eines Typs oder einer Variable benötigt wird, wird der sizeof-Operator verwendet.
Dieser gibt in der Regel die Größe des Operanden in Bytes zurück und wird beispielsweise bei der dynamischen Speicherreservierung verwendet, oder wenn Sie Programme schreiben, die auf andere Plattformen portierbar sind.
int x;int szx=sizeof(x);int szint=sizeof(int);
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Wertebereiche von Datentypen
Alle Datentypen können anders als in der Mathematik nur eine diskrete und endliche Menge von Werten darstellen (kodieren).
num2017 Wertebereiche von Ganzzahl- und Gleitkommazahlen in C. Die Bytereihenfolge ist prozessor- und betriebssystemabhängig!
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Konstanten
Benötigen Sie einen unveränderbaren Wert, können Sie eine Konstante verwenden.
#define N2 100const int N1 = 100;int x1 = N1;int x2 = N2;
Vorteil: "read only" Semantik, der Wert von konstanten "Variablen" kann zur Kompilierungszeit substituiert werden und benötigt keinen (Heap/Stack) Speicher.
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Lebensdauer und Sichtbarkeit von Variablen
Die Lebensdauer einer Variablen gilt immer bis zum Ende des Anweisungsblocks (Bezeichnerkontext, Scope), in dem sie definiert wurde. Dazu ein einfaches Beispiel:
int x=1,y=2;if (x>0) { int x=2; printf("x=%d y=%d\n",x,y);}printf("x=%d y=%d\n",x,y);
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Lebensdauer und Sichtbarkeit von Variablen
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Referenz- versa Wertsemantik
Wir unterscheiden:
x=100function foo(u) { u=200}foo(x)// x==100!
x=100function foo(reference u) { u=200}foo(x)// x==200!
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Referenz- versa Wertsemantik
Die Unterscheidung beider Semantiken kann verwirrend sein. Insbesondere in C. Hier unterscheiden wir Wert- und Zeigervariablen.
Das pardoxe in C ist aber: Auch Zeigervariablen (werden noch eingeführt) unterliegen der Wertsemantik. In C gibt es nur Wertsemantik!. Das ist i.A. typisch für statisch typisierte Programmiersprachen.
Dynamisch typisierte Programmiersprachen können teils wert- teil referenzbasiert sein.
Welche Programmiersprachen unterstützen echte Referenzsemantik bei Funktionsparametern?
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Kontrollanweisungen
Bedingte Verzweigung
if (ε) { .. }if (ε) { ..} else { .. }
Mehrfachauswahl
switch (ε) { case c1: .. break; case c2: .. break; case c3: .. break; default : ..}
Schleifen
for(i=ε;i<ε;i=ε(i)) { .. } while (ε) { .. } do { .. } while (ε)
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Schleifen
Man unterscheidet:
Zählschleife for(start;condition;update)
mit drei integrierten Anweisungen: Einer Initialisierung (einer oder mehrere Zählvariablen), einer Schleifenbedingung die vor jedem Durchlauf getestet wird (i.A. der Zählvariable), und einer Update Anweisung die nach jedem Schleifendurchlauf ausgeführt wird (i.A. Veränderung der Zählvariable).
Abweisende Schleife while(condition)
die abhängig von der Schleifenbedingung niemals, einmal oder mehrmals den Schleifenkörper ausführt (Test vor Schleifendurchlauf).
Eine nicht abweisende Schleife do {} while(condfition)
die mindestens einmal den Schleifenkörper ausführt (Test nach jedem Schleifendurchlauf).
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Beispiele von Kontrollanweisungen
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Goto Hell
Goto sollte nur sparsam (wenn überhaupt) verwendet werden und nut vorwärts verzweigende.
Modernes (vorwärts) Goto: Exceptions!
Aber: Goto kann die Lesbarkeit und Nachvollziehbarkeit von Programmen auch erhöhen wenn es keine Exceptions gibt, wie das in C der Fall ist.
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Goto Heaven
Goto nur als Ersatz für Exceptions benutzen.
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Goto Heaven
#define EOK 0#define EIO -1int IOOP(...) { int status; status=dev_open(...); if (status!=0) goto error; // raise Error status=dev_read(...); if (status!=0) goto error; // raise Error status=dev_write(...); if (status!=0) goto error; // raise Error status=dev_close(...); if (status!=0) goto error; // raise Error return EOK;error: dev_close(...); // handle Error locally return EIO; // raise EIO}
Teilschrittige Ausführung einer EA Operation mit Fehlerbehandlung
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Goto Earth
Der C2JS Transpiler kann kein Goto unterstützen da 1. JavaScript (zu Recht) kein Goto unterstützt und 2. C Anweisungsblöcke als JS Anweisungsblöocke implementiert werden (und dann wieder 1.).
Um C Goto zu unterstützen bräuchte man eine VM Loop die adressierte (Bytecode) Instruktionen verarbeitet - haben wir hier aber nicht.
Eine Krücke wäre nur die o.g. Struktur in eine try-catch-raise Struktur umzusetzen.
Analysiere den Kernel vom basekernel BS auf die Verwendung von goto (Üungsaufgabe)
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Funktionen
Eine Funktion besteht aus einem Namen, einer (optionalen) Liste von Funktionsparametern, dem Rückgabetyp, und dem Funktionsrumpf aus Anweisungen.
==== Definition ====<rettype> <name>(<partype> <parname>,...) { ... };==== (Vorwärts)Deklaration ====extern <rettype> <name>(<partype> <parname>,...);==== Aufruf ====<name>(<argexpr>,<argexpr>,..)
Funktionsdefinition in C
Stefan Bosse - Grundlagen der Betriebssysteme - Modul A C Programmierung :: Beispiele von Funktionen