Vorgänge während der Übersetzung eines C Programms C Programmierung - Vorlesung 3 Hochschule Regensburg 23.03.2012 Universitätsstraße 31, 93053 Regensburg Bei jedem Aufruf des Compilers laufen folgende Schritte ab 1 Vorverarbeitung des Source-Files (pre-processing) ◮ folgt auf einen backslash-Character (ASCII 0x5c) ein newline-Character (0xa), so werden beide Zeichen entfernt ◮ Preprocessor Direktiven (#Makros) werden ausgeführt (Textersetzung bei define, include usw.) ◮ aufeinander folgende String-Literale werden konkateniert 2 Übersetzung der resultierenden Tokens in Maschinencode 3 Auflösung externer Referenzen durch den Linker Prof. Dr. Jan Dünnweber Prof. Dr. Jan Dünnweber, Folie 2 von 18 Scope: Der Geltungsbereich von Vereinbarungen Programmierung 1 Beispiele für Vereinbarungen mit unterschiedlichem Scope Bezeichner haben unterschiedliche Geltungsbereiche Scope Type File Scope Block Scope Prototype Scope Ort der Deklaration Außerhalt aller Blöcke und Parameterlisten Zu Beginn eines Blocks bzw. in einer Funktionsdefinition In einem Funktionsprototyp Geltungsbereich Global, bis zum Ende der Datei bzw. Übersetzungseinheit Lokal, bis zum Ende des Blocks bzw. der Funktion nirgends (!), die Bezeichner haben Kommentar-Charakter Anmerkung: Eine Ausnahme stellen Labels dar. Diese haben Funktion Scope, d.h. Geltung in einer Funktion, unabhängig von der Blocktiefe Es gilt jedoch: Labels haben i.A. nur Kommentar-Charakter (Verwendung im Zusammenhang mit ////// goto ist nicht weiter relevant) Prof. Dr. Jan Dünnweber, Folie 3 von 18 Programmierung 1 Definition einer einfach verketteten Liste struct Node { /∗ ... ∗/ struct Node ∗next; }; void printNode(const struct Node ∗ptrNode); int printList(const struct Node ∗first) struct Node ∗ptr = first; while (ptr != NULL) { printNode(ptr); ptr = ptr−>next; } } { Node, next, printNode & printList haben alle File Scope ptrNode hat Prototype Scope first & ptr haben Block Scope Prof. Dr. Jan Dünnweber, Folie 4 von 18 Programmierung 1 Namensräume und Bezeichner Vorzeichenbehaftete Typen und Synonyme Jeder Bezeichner fällt in eine der folgenden Kategorien Label mit Kommentar-Charakter Tag zur Markierung von structure- und union-Typen Member Name innerhalb von structure- und union-Typen Gewöhnliche Bezeichner Da jeder Kategorie ein Namensraum (namespace) zugeordnet ist, kann ein Bezeichner konfliktfrei mehrere kontextabhängige Bedeutungen haben Beispiel struct pin { char pin[16]; /∗ ... ∗/ } _Bool check_pin(struct pin ∗pin) { int len = strlen(pin−>pin); } Die primitiven Typen haben z. T. (mehrere) Synonyme Typ Synonym signed char int signed, signed int short short int, signed short, signed short int long long int, signed long, signed long int long long long long int, signed long long, signed long long int pin referenziert hier sowohl den Typ (Tag struct pin) als auch das zugehörige char-Array (Member char pin[]) und einen Zeiger auf eine Variable (Member char pin[]) Mittels pin->pin wird die Member-Variable wieder dereferenziert Prof. Dr. Jan Dünnweber, Folie 5 von 18 Programmierung 1 Vorzeichenlose Typen und Synonyme Auch für vorzeichenlose Primitive gibt es z. T. (mehrere) unteschiedliche Schreibweisen (Synonyme) Typ Synonym unsigned char char unsigned int int unsigned short unsigned short int unsigned long unsigned long int unsigned long long unsigned long long int Prof. Dr. Jan Dünnweber, Folie 7 von 18 Programmierung 1 Prof. Dr. Jan Dünnweber, Folie 6 von 18 Programmierung 1 Der Spezielle Typ void void hat (kontextabhängig) unterschiedliche Bedeutungen Kontext Beispiel Bedeutung in einer void exit(void) Die Funktion Funktionsdeklaration Ausdruck (void)exit(); vom Typ void Zeiger auf void void *malloc(size t t); void free(void *ptr); Prof. Dr. Jan Dünnweber, Folie 8 von 18 hat kein Ergebnis und keine Parameter Der Ausdruck hat keinen Wert Reservierung bzw. Freigabe von Speicher unbestimmten Typs Programmierung 1 Array-Typen: Motivation Verwendung von Arrays, Regeln Addition von Zahlen Es ist v. a. wichtig, auf korrekte Indizierung zu achten #include <stdio.h> int main(int argc, char∗ argv[]) { int x,y,z,sum; x = 1; y = 2; z = 4; sum = x + y + z; printf("summe = %d \n", sum); /∗ Summe ausgeben ∗/ return 0; } Felder (engl. Arrays) werden durch eckige Klammern hinter dem Variablennamen deklariert Auf die einzelnen Elemente des Feldes wird dann mit einem Index zugegriffen Beim Feld mit N Elementen erfolgt die Zählung von 0 bis N-1 Alternativ lassen sich die Werte in einem Array zusammen fassen Addition der Feldelemente #include <stdio.h> int main(int argc, char∗ argv[]) { int sum, feld[3]; feld[0] = 1; feld[1] = 2; feld[2] = 4; sum = feld[0] + feld[1] + feld[2]; printf("summe = %d \n", sum); /∗ Summe ausgeben ∗/ return 0; } Prof. Dr. Jan Dünnweber, Folie 9 von 18 Programmierung 1 Auswertung der Kommandozeile Auch bei der Kommandozeilenauswertung wird ein Array verwendet Das Betriebsystem übergibt dem Programm zwei Werte 1 Die Anzahl der Argumente über den Formalparameter: int argc 2 Ein Feld von Zeigern auf Zeichenketten via char *argv Beispiel: Array mit 3 primitiven Elementen int feld[3]; feld[0] = 1; feld[1] = 2; feld[2] = 3; ///////////////// feld[3] = 4;/ Ein Array ist ein homogener Verbundtyp invariabler Größe Prof. Dr. Jan Dünnweber, Folie 10 von 18 Programmierung 1 Ausgabe aller Parameter Vereinbarte Parameter (z. B. in Prototypen) heißen auch Formalparameter, die tatsächlich übergebenen Argumente nennt man Aktualparameter (en. formal/actual) Im folgenden sollen alle Argumente (Aktualparameter) in der Konsole ausgegeben werden Beispiel: Einfaches echo-Programm Beispiel: Einfaches wc-Programm (word count) #include <stdio.h> int main(int argc, char∗ argv[]) { printf("# Argumente = %d \n", argc); return 0; } #include <stdio.h> int main(int argc, char∗ argv[]) { printf("# Argumente = %d \n", argc); printf("0. Arg = %s \n", argv[0]); printf("1. Arg = %s \n", argv[1]); printf("2. Arg = %s \n", argv[2]); return 0; } Was passiert bei weniger als 3 Argumenten? Prof. Dr. Jan Dünnweber, Folie 11 von 18 Programmierung 1 Prof. Dr. Jan Dünnweber, Folie 12 von 18 Programmierung 1 Die while-Schleife NullPointer Parameterwert argv[0] enthält den Programmnamen Die Parameter argv[1], ... , argv[argc-1] enthalten die Argumente Jeder Zugriff auf höherwertige Arrayindices führt zu Fehlern, i. A. null-Pointer Die while-Schleife (en. loop) ist eine der einfachsten Kontrollstrukturen in C und dient der repetitiven Evaluation von Ausdrücken (≈ wiederholt) z. B. zur iterativen Verarbeitung (≈ schrittweise) von Verbundtypen wie Arrays Die Syntax der while-Schleife sieht aus, wie folgt Beispiel: Zugriff auf ein undefiniertes Array-Element Formale Spezifikation der while-Schleife in C ./ausgabe bla # Argumente = 2 0. Arg = ./ausgabe 1. Arg = bla 2. Arg = (null) while( <expr> ) /∗ Kopf/Head(er) bzw. Definition ∗/ statement; /∗ Rumpf/Body bzw. abhaengige Anweisung(en) ∗/ Arraybezeichner (Identifier) sind nur Zeiger (Pointer) auf das erste Element des Arrays, beim Zugriff mittels Index wird ein Vielfaches der Elementgröße addiert. Dementsprechend sind Indizierungen über Arraygrenzen hinaus zulässig (sogar negative Indices) aber nur selten sinnvoll (⇒ i. A. zu vermeiden!) Prof. Dr. Jan Dünnweber, Folie 13 von 18 Programmierung 1 Auswertung der while-Schleife Wann ist <expr> wahr bzw. wann und ggf. wie lange wird der Body der while-Schleife ausgeführt? Sofern das statement im Body der while-Schleife aus mehreren Anweisungen besteht, müssen diese von geschweiften Klammern ({}, d. h. als Block) umgeben sein Aus Gründen der Lesbarkeit empfiehlt es sich, immer einen Block für den Body zu verwenden Prof. Dr. Jan Dünnweber, Folie 14 von 18 Programmierung 1 Parameterausgabe mittels while-Schleife Der Body der while-Schleife wird so lange ausgeführt, bis die Bedingung index < argc verletzt ist Laufzeitbedingungen in der while-Schleife Echo Programm mit while-Schleife while( <expr> ) /∗solange <expr> == "true" ... ∗/ statement; /∗ evaluiere body ∗/ int main(int argc, char ∗argv[]) { int index; index = 0; printf("# Args %d \n",argc); while(index < argc) { printf("%d. Arg %s\n",index,argv[index]); index = index + 1; } return 0; } In C gibt es keinen boolschen Datentyp für false und true 0 entspricht in C dem logischen Wert false, jeder beliebige andere Wert entspricht true ⇒ Die Anweisung statement wird so lange ausgeführt, bis das Ergebnis von <expr> gleich 0 ist Prof. Dr. Jan Dünnweber, Folie 15 von 18 Programmierung 1 So wird ein unzulässiger Index vermieden Prof. Dr. Jan Dünnweber, Folie 16 von 18 Programmierung 1 weiteres Beispiel: Berechnung des Mittelwerts Was haben wir gelernt? Der while-header <expr> enthält nicht immer einen Zähler top-driven average computation Zusammenfassung /∗ Read in numbers from the keyboard and ∗ print out their average. ∗/ #include <stdio.h> int main(int argc, char∗ argv[]) { double x = 0.0, sum = 0.0; int count = 0; printf( "\t−−− Calculate Averages −−−\n" ); printf( "\nEnter some numbers:\n" "(Type a letter to end your input)\n" ); while ( scanf( "%lf", &x ) == 1 ) { sum += x; ++count; } if ( count == 0 ) printf( "No input data!\n" ); else printf( "The average is %.2f\n", sum/count ); return 0; } Bezeichner haben in C kontextabhängige Geltungsbereiche (Scope) und Bedeutung (Semantik) Bereits in einfachen Ausdrücken ist elemtare Pointer Arithmetik unvermeidbar Um Konflikte zu vermeiden, sollte man Namensäume und Typen (inkl. möglicher Synonyme) gut kennen Ein einfacher Verbundtyp ist das Array (≈ Feld) Eine einfache Kontrollstruktur ist die while-Schleife Die while-Schleife wird oft zusammen mit Arrays für Iterationen benutzt c O’Reilly “C In A Nutshell” (p. 85) Prof. Dr. Jan Dünnweber, Folie 17 von 18 Programmierung 1 Prof. Dr. Jan Dünnweber, Folie 18 von 18 Programmierung 1