Erster Teil des Intensivkurses C [Erster Teil des Intensivkurses C] (Erster Teil des Intensivkurses C), Lektion, Seite 722303
https://www.purl.org/stefan_ram/pub/erster_teil_des_intensivkurses_c (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Erster Teil des Intensivkurses C 

Inhalte des ersten Termins (2009-11-16)

Vorbemerkung: Bei Bedarf können auch Perl-Programme „übersetzt“ werden

Inhalte des zweiten Termins (2009-11-18)

Empfehlenswerte Literatur zur Programmiersprache C
Programmieren in C; Brian W. Kernighan, Dennis M. Ritchie; ISBN-10: 3-446-15497-3; ISBN-13: 978-3-446-15497-1; http://www.hanser.de/buch.asp?isbn=3-446-15497-3
Informationen zu vim
vim cheat
vim tutorial
man vim
:help
Einige häufig verwendete Kommandos im Grundmodus
i Einfügemodus (verlassen mit Esc)
A Am Ende einer Zeile anfügen
r ein einzelnes Zeichen ändern
R zum Überschreibemodus wechseln
x ein einzelnes Zeichen löschen
cw Rest des aktuellen Wortes verändern
Zahl  Wiederholung des folgende Kommandos
u Änderung zurücknehmen
0 Zum Anfang der Zeile gehen
$ Zum Ende der Zeile gehen
: Zur Eingabe für erweiterte Kommandos wechseln
:Zahl  Zur angegebenen Zeile gehen
:w Datei speichern
:w Name  Datei Name speichern
:w! Datei überschreiben
:q vim beenden
:q! vim ohne speicher beenden
:wq Speichern und beenden
/ Zur Eingabe für Suchtext wechseln
/Text Text  suchen
Terme
 1     3      3 + 4        3
--- - ---, -------, 2 ---
2 4 5 - 6 7
Definition einfacher Wirkfunktionen (Uebungsaufgabe)
Tomaten
Rotkohl Gruenkohl
Gurken
Tomaten
Rotkohl Gruenkohl
Spinat
Tomaten
Rotkohl Gruenkohl
Kohlrabi

Inhalte des dritten Termins (2009-11-23)

>721377 Zeilenformatierung in C
>721251 Zahlenformatierung in C
>721411 Dokumentationskommentare in C
>720988 Präprozessor *
>721386 Vorverarbeiterdirektiven in C
>721413 Laufzeitoptimierte Funktionsaufrufe in C
>721422 Wertrückgabe
>721507 Die Wertrückgabe in C

>720570 getrennte Übersetzung *
>721419 getrennte Übersetzung in C
>721209 Konstantendefinitionen
>721502 Konstantendefinitionen in C
>721513 Konstanten mit Dateigültigkeitsbereich in C
>721467 Blockkonstanten
>721519 Blockkonstanten in C
>720836 Parameter
>720840 Parameter-Definitionen in C

Inhalte des vierten Termins (2009-11-30)

Parameter-Definitionen in C

Übungsaufgaben

/    Parametrisierter Würfel (Nachbesprechung frühestens 2009-12-02)
Schreiben Sie eine Definition einer Funktion »dice« mit zwei Parametern (jeweils vom Typ »int«) und einem Ergebnis vom Typ »int«, so daß der Wert des Ausdrucks »dice(i ,j )« eine (ungefähr gleichverteilte) ganzzahlige Zufallszahl zwischen 〈i 〉 (einschließlich) und 〈j 〉 (einschließlich) ist.
/    Ganzzahliger Mittelwert zweier ganzzahliger Werte (Nachbesprechung frühestens 2009-12-02)
Schreiben Sie eine Definition einer Funktion »avg« mit zwei Parametern (jeweils vom Typ »int«) und einem Ergebnis vom Typ »int«, so daß der Wert des Ausdrucks »avg(i ,j )« die größte ganze Zahl kleiner-gleich dem Mittelwert der Werte der beiden Argumentausdrücke »〈i 〉« und »〈j 〉« (jeweils vom Typ »int«) ist.
Testen Sie Ihre Definition unter anderem an Hand der Werte der in der folgenden Liste vor dem Semikolon stehenden Ausdrücke (nach »#include <limits.h>«):
»avg(0,0)«; 0
»avg(1,0)«; 0
»avg(0,1)«; 0
»avg(1,1)«; 1
»avg(INT_MIN,INT_MIN)«; INT_MIN
»avg(INT_MIN,INT_MAX)«; Unter einer typischen C -Implementation wird ein Wert nahe 0 (also 0, +1 oder −1) erwartet
»avg(INT_MAX,INT_MIN)«; Unter einer typischen C -Implementation wird ein Wert nahe 0 (also 0, +1 oder −1) erwartet
»avg(INT_MAX,INT_MAX)«; INT_MAX
Der gesuchte Mittelwert soll aber natürlich nicht nur für diese acht Testwerte, sondern für alle möglichen int -Werte richtig berechnet werden. Um dies zu testen, könnten daher noch weitere Testwerte herangezogen werden.

Aufrufe von gcc

Allgemeine Warnung Wenn eine Datei erzeugt wird, dann wird eine schon unter dem Namen dieser Datei vorhandene Datei oft ohne Nachfrage ersetzt, also gelöscht. Damit enthalten viele Kommandos ein verstecktes Löschkommando. Deswegen muß man nicht nur vorsichtig sein, wenn man »rm« direkt aufruft.

Übersetzen und Binden einer Quelldatei (nach »a.out«)
gcc example.c
Übersetzen ohne Binden (erzeugt die Objektdatei »example.o«)
gcc -c example.c
Binden der Objektdatei (erzeugt die ausführbare Datei »a.out«)
gcc example.o
Ausgabe in die Datei »example« statt »a.out«
gcc -o example example.c

gcc -o example example.o
Binden mehrerer Dateien, gegebenenfalls mit vorherigem Übersetzen
gcc example.c example1.c example2.o example3.o

Einige Optionen

-c Nur in Objektdatei übersetzen (nicht binden)

-lm Mathematik-Bibliothek dazubinden

-x c Angabe der Programmiersprache C  (Alternativen sind unter anderem »-x c«, »-x c-header«, »-x c++«)

-std=c99 Aktuelle C Version (Eine Alternative ist unter anderem »-std=c89« für eine ältere Version)

-pedantic Genauere Prüfungen der Einhaltung der Sprachspezifikation

-Wall Mehr Prüfungen für Warnungsmeldungen durchführen

-Wextra Zusätzliche weitere Prüfungen für Warnungsmeldungen durchführen

Option Index
Beschreibung weiterer Kommandozeilenoptionen
http://gcc.gnu.org/onlinedocs/gcc/Option-Index.html

Eine einfache Makefile

In einer Makefile  müssen die eingerückten Zeilen mit einem TAB-Zeichen (Zeichenkennzahl 7) eingerückt sein. Manche Editoren interpretieren die TAB-Taste aber als Kommando oder ersetzen TAB-Zeichen durch Leerzeichen, so daß das Editieren einer Makedatei nicht immer ganz einfach ist.

vim
:set noexpandtab
:set nosmarttab

Eine Makefile  mit impliziten Regeln

Makefile
CC=gcc

CFLAGS=-Wall



example: example.o



clean:
rm -f example example.o
Kommando zum Erzeugen
make
Kommando zum Aufräumen
make clean

Eine Makefile  mit expliziten Regeln

Makefile
CC=gcc

CFLAGS=-Wall



example: example.o

    gcc -o example main.o



example.o: example.c

    gcc -c -o example.o example.c
Kommando
make

Objekte in C

Zuweisungen in C

Ausdruck »〈L  = R 〉«
Wert der Zuweisung Der Wert 〈R 〉 gewandelt in den Typ von 〈L 
Wirkung einer Zuweisung Der Wert der Zuweisung wird nach 〈L 〉 geschrieben

Übungsaufgaben

/    Wert einer Zuweisung
void f( int const w ){ char v; printf( "%d\n",( v = w )== ?  ); }
Welcher Ausdruck sorgt an der Stelle des Fragezeichens 〈? 〉 dafür, daß bei der Auswertung des Ausdrucks »f(zulässiger Ausdruck )« stets »1« ausgegeben wird?

Verkürzte Zuweisungen in C

Links- und Rechtswerte in C

Es ist verständlich, daß die Zuweisung »v = 5« möglich ist, aber die Zuweisung »5 = v« nicht möglich ist, da »5« kein veränderbares Objekt angibt. Man sagt dazu in C  auch, daß »5« kein „Lwert“ sei, sondern nur ein „Rwert“ sei. Ein Variablenname, wie »v«, kann sowohl Lwert als auch Rwert sein, so daß »v = v« möglich ist, dabei steht »v« links für das Objekt »v« und rechts für den darin gespeicherten Wert.

Undefiniertes Verhalten in C

Wenn eine Spezifikation einer Programmiersprache für einen bestimmten Fall kein bestimmtes Verhalten verlangt, so wird dieses als „undefiniertes Verhalten “ bezeichnet. Da es bei undefiniertem Verhalten auch zulässig ist, wenn eine Implementation dieser Programmiersprache die Festplatte löscht oder irgendetwas Beliebiges  mit beliebig katastrophalen Folgen macht, gilt es als schwerer Fehler, wenn ein Programmierer Quellcode schreibt, dessen Ausführung zu undefiniertem Verhalten führt. Das ist in C  jedoch leider oft recht einfach.

Das Erkennen von undefiniertem Verhalten ist schwierig, da es weder immer durch Fehlermeldungen gemeldet wird, noch durch Ausprobieren oder Testen zuverlässig erkannt werden kann. Die besten Mittel gegen undefiniertes Verhalten sind eine gute Ausbildung und die nötige Sorgfalt.

Weniger schlimm ist unspezifiziertes Verhalten, bei dem die Spezifikation einige verschiedene Möglichkeiten zuläßt (aber nicht alles Beliebige).

Sequenzpunkte in C

In C  gibt es sogenannte „Sequenzpunkte“, wie beispielsweise das Ende einer Anweisung, zu denen alle Wirkungen einer Auswertung eingetreten sind. Es führt jedoch zu „undefiniertem Verhalten“, wenn versucht wird, ein Objekt zwischen zwei Sequenzpunkten mehr als einmal zu verändern. Der Ausdruck »(i = ++i)« enthält gar keinen Sequenzpunkt, sein Auswertung hat also undefiniertes Verhalten. Auch die Anweisung »i = ++i;« ist daher falsch.

Übungsaufgaben zu Variablen

/    Vertauschen der Werte zweier Objekt
{ int x = 22;
int y = 47;
/* Hier die Werte von x und y vertauschen */
printf( "%d %d\n", x, y ); /* Ausgabe: 47 22 */ }
/    Vertauschen der Werte zweier Objekt mit einer Funktion
{ int x = 22;
int y = 47;
swap( x, y ); /* Ist solch eine Funktion moeglich? */
printf( "%d\n%d\n", x, y ); /* Ausgabe: 47 22 */ }

Parametrisierte Makros des C -Präprozessors

Namen für Objekte mit statischer Lebensdauer

»extern int name;« ist eine Deklaration für einen Namen »name« mit externer Bindung und vom Typ »int«, entsprechend auch für andere Namen und Datentypen. Eine Deklaration in dieser Schreibweise ist stets eine reine Deklaration  ohne Objektdefinition. Sie stellt also keinen Speicher bereit.

Eine Deklaration ohne »extern« und mit Initialisierung, wie »int alpha = 0;«, ist stets immer auch eine Objektdefinition, sie stellt also auch Speicherplatz bereit.

Bei getrennter Übersetzung sollte es zu einem Namen genau eine Objektdefinition in einer Übersetzungseinheit geben und kann es beliebig viele reine Deklarationen desselben Namens geben, die dann vom Binder als Bezüge auf das einmal definierte Objekt aufgelöst werden.

Andere Schreibweisen, wie etwa »int alpha;« (sowohl ohne »extern« als auch ohne Initialisierung) oder »extern int alpha = 0;« (mit »extern« und Initialisierung) sind komplizierter zu interpretieren und sollten daher vermieden werden. (Innerhalb eines Blocks kann die erste Schreibweise aber legitim sein.)

Die Deklaration »static int alpha = 0;« deklariert ein Objekt mit statischer Lebensdauer und dem Anfangswert 0. Steht sie außerhalb eines Blocks, so hat der Name interne Bindung, steht sie innerhalb eines Blocks, so hat der Name keine Bindung.

Übungsaufgaben zu Objekten mit statischer Lebensdauer

/    Aufrufzähler
Schreiben Sie eine Definition einer Funktion, die mitzählt wie oft sie aufgerufen wurde. Dabei soll der Gültigkeitsbereich der dazu verwendeten Zählvariablen möglichst klein sein.

Verzweigungen

selection-statement 〉 ::=
"if" "(" 〈condition 〉 ")" 〈statement 〉.
selection-statement 〉 ::=
"if" "(" 〈condition 〉 ")" 〈statement 〉 "else" 〈statement 〉.
Im Gegensatz zu Perl  muß kein Block verwendet werden, dieser kann aber nötig werden, wenn mehrere Anweisungen zu einer zusammengefaßt werden sollen.
Semantik
bedingte Anweisung
int main( void ) 
{ if( 0 )printf( "ab\n" );  
if( -2 )printf( "cd\n" ); }

Ausgabe? Übersetzung in Maschinensprache?
Übungsaufgabe Ein Programm soll „Hallo, Welt!“ ausgeben und dessen Quelltext soll kein Semikolon enthalten.
Kehrwert
int kehrwert( int const wert ) 
{ if( wert )printf( "%g\n", 1. / wert ); 
else printf( "nicht definiert\n" ); }

Die folgende Funktionsdefinition soll ein Prädikat definieren, das ergibt „ob ein Wert 0 ist“. Es sollen eventuell vorhandene Fehler korrigiert, und es soll die Definition vereinfacht werden.
is0
bool is0( int const v ) 
{ if( v = 0 )result = 1; 
else result = 0; 
return result; }

Die folgende Anweisung soll vereinfacht werden (»zahl« habe den Datentyp »int«).
Nicht Null
if( zahl != 0 )printf( "nicht Null\n" );

/    Einfache Rekursion
Schreiben Sie eine Definition einer Funktion »ast« mit einem Parametern vom Typ »int«, so daß bei der Auswertung von »ast(i )« 〈i 〉 Sternchen ausgegeben werden (beispielsweise »***«, wenn 〈i 〉 den Wert 3 hat).

Schleifen

iteration-statement 〉 ::=
"while" "(" 〈condition 〉 ")" 〈statement 〉.
Semantik
Funktion kgv 
mit ganzzahligem Ergebnis,  
dem ganzzahligen Eingangsparameter a und  
dem ganzzahligen Eingangsparameter b 
.--------------------------------------------------. 
|Mit den ganzzahligen Variablen x und y. | 
| .-----------------------------------------------| 
| | x := a; y := b; | 
| |-----------------------------------------------| 
| | Wiederhole solange x ungleich y | 
| | .--------------------------------------------| 
| | | Ist x < y? | 
| | | Ja | Nein | 
| | |---------------------|----------------------| 
| | | x := x + a | y := y + b | 
| |-----------------------------------------------| 
| | Das Ergebnis ist x. |
'--------------------------------------------------'

Inhalte des fünften Termins (2009-12-02)

Ergänzungen zum Editor „vim“

Weitere Bewegungen (zusätzlich zu h, j, k und l)
gg Textanfang
G Zeile (beispielsweise 12G), G alleine: Textende
ctrl-o Zurückgehen
0 Zeileanfang
^ Textzeilenanfang
$ Zeilenende
b Wortanfang
e Wortende
w nächstes Wort
% zugehörige Klammer
tx  Zeichen x  (ausschließlich)
fx  Zeichen x  (einschließlich)
Textspeicher-Kommandos
y Kopieren („yank“)
d Ausschneiden („delete“)
p Einfügen hinter  der aktuellen Position („paste“)
P Einfügen vor  der aktuellen Position („paste“)

Diesen Kommandos folgt ein Bewegungskommando, wie 2w („zwei Wörter“). diw löscht, ciw ändert das gesamte  Wort.

Die Textspeicher-Kommandos können aber auch einer visuellen Selektion  folgen.

v visuelle Selektion (danach: Esc, y, oder d)

:registers Anzeige der Register

Interaktion mit Register a
"ap Einfügen aus Register a
"ay Einfügen in das Register a
Interaktion mit der Systemablage (sollte aber nicht über ssh möglich sein)
shift-insert Einfügen aus der Systemablage
"*p Einfügen aus der Systemablage
"+y Einfügen in die Systemablage
Löschen ohne Registerveränderung
"_d… Löschen ohne Veränderung eines Registers
Suchen
* aktuelles Wort
/muster  Textmuster muster  suchen
n nächstes Vorkommen
N früheres Vorkommen
:set incsearch Inkrementelle Suche
Spracheunterstützung
:syntax on Syntaxeinfärbung aktivieren
:set filetype=C Sprache festlegen
Mehr oder weniger häufig benötigte Kommandos
~ Groß- und Kleinschreibung vertauschen
xp Zwei Zeichen vertauschen (cut and paste)
dwwP Zwei Wörter vertauschen
gf Wort als Dateinamen laden
. Letztes Kommando wiederholen
@: Letztes Zeilenkommando wiederholen
:tabedit filename  Tab-Edit, shift-Page
:ab dns Desoxyribonukleinsäure Abkürzung
vim .exrc Bearbeiten der Startdatei
Aufzeichnungen
:earlier 4m 4 Minuten zurück
:later 45s 45 Sekunden vor
:undo 5 5 Schritte zurück
:undolist Liste ansehen
A Byte of Vim
Swaroop C H
http://www.swaroopch.com/notes/Vim

Reihungen und Zeiger

Zeichenfolgen

Inhalte des 6. Termins (2009-12-14)

Nachtrag zu Prototypen

Im allgemeinen ist in C  (nicht aber in C++ ) »int main( void )« besser als »int main()«, da die erste Schreibweise einen „Prototyp“ definiert und es so dem Compiler erlaubt, Aufrufe besser zu prüfen. »int main()« würde eine Funktion mit einer unbestimmter Zahl von Argumenten mit unbestimmten Typ deklarieren.

Konsole
$ head test.c | cat -n

     1  int f( void );
2 int g();
3 int main( void )
4 {
5 g( 1 );
6 f( 2 );
7 }
8 $ mcc test.c:2: warning: function declaration isn't a prototype
test.c: In function 'main':
test.c:6: error: too many arguments to function 'f'

Die Verwendung von »void« in »int main( void )« wird so auch ausdrücklich in ISO/IEC 9899:1999 (E), 5.1.2.2.1#1 angegeben:

Zitat aus ISO/IEC 9899:1999 (E), 5.1.2.2.1#1
It shall be defined with a return type of int and with no parameters:
int main(void) { /* ... */ }
or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):
int main(int argc, char *argv[]) { /* ... */ }
or equivalent; or in some other implementation-defined manner.

Allerdings findet sich in derselben Norm in Beispielen auch »int main()«, was tatsächlich dann tolerabel ist, wenn »main« nicht im Rest des Programms aufgerufen werden soll.

Nachbesprechung von Übungsaufgaben

Nachtrag zu Makros

Nachtrag zu Zeichen

Nachtrag zu Zeigern und Ketten

Die switch -Anweisung

selection-statement 〉 ::=
"switch" "(" 〈expression 〉 ")" 〈statement 〉.

Der ganzzahlige Ausdruck 〈expression 〉 bestimmt, daß die Ausführung bei einer bestimmten Anweisung innerhalb des Rumpfs 〈statement 〉 fortgesetzt wird. Nämlich bei einer case -Markierung mit dem Wert dieses Ausdrucks, die sich in der folgenden Anweisung befindet und zu der diese switch -Anweisung die nächste umgebende ist. Eine solche case -Markierung ist auch nur innerhalb einer switch -Anweisung erlaubt.

labeled-statement 〉 ::=
"case" 〈constant-expression 〉 ":" 〈statement 〉.

Dabei ist die ganzzahlige 〈constant-expression 〉 ein konstanter Ausdruck.

Wird kein passender Wert gefunden, wird die Ausführung bei einer Fehlmarke (»default«) fortgesetzt, die erlaubt ist, wo auch eine case -Marke erlaubt wäre.

labeled-statement 〉 ::=
"default" ":" 〈statement 〉.
Beispiel
#include <stdio.h> /* printf */

void f( int const x ){}

int main( void )
{ int const expr = 2; /* 1, 0 */
int const a = 1; /* a ist keine "constant expression" */
switch( expr )
{ int i = 4; f( i ); switch( i ){ case 1:; }
/* case a: i = 9; */
case 0:
i = 17;
{ case 2: i = 3; }
default:
printf( "%d\n", i ); }}

In dem obigen Programmbeispiel ist der Wert der Variablen »i« unter Umständen unbestimmt, und die Funktion »f« wird nie aufgerufen.

Sprunganweisungen für Schleifen
jump-statement 〉 ::=
"break" ";".

Hiermit wird die nächste umgebende switch -Anweisung oder Schleife verlassen (beendet).

Beispiel einer break -Anweisung nach einer case -Marke:
case 0:
{ i = 17;
break; }
jump-statement 〉 ::=
"continue" ";".

Hiermit wird zum Ende des Rumpfs (also oft bis zur schließenden Klammer »}«) der nächsten umgebenden Schleife gesprungen.

Die for -Anweisung

iteration-statement 〉 ::=
"for" "(" [〈clause 〉] ; [〈condition 〉]; [〈expression 〉] ")" statement .

Die Klausel clause  wird einmalig am Anfang der Schleife ausgewertet, falls sie ein Ausdruck ist. Sie kann auch eine Deklaration sein, die dann für den Rest der Schleife gilt.

Der Ausdruck condition  wird wie in einer while-Schleife interpretiert. Falls er fehlt gilt er als ungleich 0.

Der Ausdruck expression  wird nach jeder Ausführung der Anweisung statement  ausgewertet, falls er vorhanden ist.

Mit »for(;;)« kann dementsprechend eine Art von Endlosschleife eingeleitet werden. Nach »#define ever (;;)« kann man auch schreiben »for ever«, was aber umstritten ist.

Beispiel
#include <stdio.h> /* printf */

int main( void )
{ { int i = 0; while( i < 4 ){ printf( "%d\n", i ); ++i; continue; ++i; }}
for( int i = 0; i < 4; ++i ){ printf( "%d\n", i ) ;++i; continue; }}
stdout
0
1
2
3
0
2
Die do -Anweisung
iteration-statement 〉 ::=
"do" statement "while" "(" expression 〉] ")" ";".

Diese Anweisung ist eine Schleife, die sich fast wie eine while -Anweisung verhält, nur daß die enthaltene Anweisung statement  zunächst immer einmal begonnen (und unter Umständen mehr oder weniger ganz ausgeführt) wird und die Anweisung expression  erst danach zum ersten Mal ausgerwertet wird, um zu ermitteln, ob eine Wiederholung folgen soll.

Sieht man ein »while« mit Semikolon, wie »while( i++ );«, so muß dies also nicht unbedingt eine while -Anweisung mit leerem Rumpf sein, es kann auch das Ende einer do -Anweisung sein.

Weitere Typen für ganze Zahlen

Ganzzahlige Datentypen und Zeigertypen werden zusammen auch als „skalare Typen“ bezeichnet (sie haben offenbar Gemeinsamkeiten, etwa die Möglichkeit, ihre Differenz anzugeben).

Die Programmiersprache C  kennt zunächst folgende ganzzahlige Datentypen: »char«, »short int«, »int«, »long int«, »long long int«.

Diese können noch mit »signed« und »unsigned« modifiziert werden. Unmodiziert enthalten sie ein Vorzeichen, außer »char«, das als »signed char« oder »unsigned char« implementiert sein kann. In manchen Fällen kann die Angabe »int« entfallen: »short«, »unsigned«, »long« und »long long«.

/    Bestimmung des Verhaltens anhand der Spezifikation
Für die beiden folgenden Anweisungen soll jeweils mit Quellenangabe ermittelt werden, welches Verhalten die Spezifikation der Programmiersprache C, ISO/IEC 9899:1999 (E), für sie festlegt. Dabei kann einer der im Web vorhandenen Entwürfe verwendet werden.
{ int i = INT_MAX; printf( "%d\n", i + 1); printf( "a\n" ); }

{ unsigned int i = UINT_MAX; printf( "%u\n", i + 1); printf( "a\n" ); }

Programmierstil: Vorbedingungen prüfen

Viele Funktionen der Standardbibliothek haben undefiniertes Verhalten für den Fall, daß ihre Vorbedingungen verletzt sind, also beispielsweise im Fall von Argumentwerten außerhalb eines bestimmten Bereichs. Dies erlaubt es ihnen schnell zu arbeiten, ohne erst noch ihre Vorbedingungen prüfen zu müssen.

Sollten Funktionsdefinitionen auch so geschrieben werden?

Eine Prüfung von Vorbedingungen kann dem „defensiven“ Programmieren entsprechen, aber zeitverschwenderisch sein, wenn diese außerhalb einer Schleife schon einmal geprüft wurde.

Beispiel
if( i != 0 )while( j = 0; j < 32767; ++j )div( j, i )

Als Kompromiß können Sicherheitsprüfungen nur während der Programmentwicklung aktiviert werden.

Beispiel
#ifdef DEBUG
if( i == 0 )printf( "Division by zero in 'div'.\n" );
#endif

Außerdem sollten Werte, die von außen in ein Programm gelangen immer mindestens einmal geprüft werden. In diesem Abschnitt ging es nur um die Frage zusätzlicher Prüfungen, für programminterne Werte, die bei korrekter Programmierung immer im richtigen Bereich liegen sollten.

Einlesen mit scanf

Die Standardfunktion »scanf« implementiert die Analyse und Umwandlung von Texten, die erst zur Laufzeit eingelesen werden.

Für die Analyse von Eingaben in länger eingesetzten Programmen ist es oft robuster, die Eingabe selber zu analysieren, wenn sich diese nicht mehr auf die von scanf  erledigten Standardfälle beschränken.

Im einfachsten Fall liest man mit der Direktive »%d« ein Dezimalnumeral in ein int -Objekt und mit »%lf« ein Gleitkommanumeral in ein double -Objekt. Die Direktive »%31s« liest einen Text mit bis zu 31 Zeichen in ein char[] -Objekt, wobei das Einlesen bei Leerraum endet und an das Ende des eingelesenen Textes ein Nullzeichen »'\0'« angefügt wird.

Die Objekte werden jeweils durch ihre Adressen angegeben.

In dem folgenden Programmbeispiel stellt »fflush( stdout )« die Sichtbarkeit der zuvor ausgegebenen Textes sicher.

Falls alle Einträge korrekt gelesen werden konnten, ergibt »scanf« die Anzahl der Einträge. Ein robustes Programm wird auch nur in diesen Fall, von den Werten der Objekte so Gebrauch machen als ob das Einlesen gelungen sei.

Beispiel
#include <stdio.h> /* printf, fflush, stdout, scanf */

int main( void )
{ int i; double x; char s[ 8 ]; double * const p = &x;
printf( "? " ); fflush( stdout );
if( 3 == scanf( "%d%lf%7s", &i, p, s ))
printf( "%d\n%f\n%s\n", i, x, s ); }
stdin
? 3 3.7
0123456789
stdout
3
3.700000
0123456

Ein häufiger Flüchtigkeitsfehler ist das Vergessen des Adreßoperators in scanf -Aufrufen. Wie man in dem Beispiel sieht, kann man aber auch nicht schematisch sagen, daß dieser Operator dort immer verwendet werden muß.

/    Bestimmung des Verhaltens anhand der Spezifikation
Eine Funktion soll eine „Würfelzahl“ (eine Zahl zwischen 1 und 6) einlesen und zurückgeben. Bei falschen oder unverständlichen Eingaben soll der Bediener die Eingabe erneut versuchen müssen.
/    Würfelsimulation
Ein Programm erstellt eine Statistik der Verteilung von 1000 simulierten Würfelwürfen. (Diese Aufgabe sollte eigentlich schon weiter oben stehen, da sie nichts mit Einlesen zu tun hat.)
/    Zahlenliste beliebiger Länge umkehren (schwierig)
Einlesen einer Folge nichtnegativer Zahlen, die durch die Eingabetaste getrennt und durch eine negative Eingabe beendet werden, Ausgabe in umgekehrter Reihenfolge (Im Idealfall sollte das Programm weder eine bestimmte feste Grenze für die Anzahl der eingegebenen Zahlen festlegen, noch diese zuvor vom Bediener erfragen.)
4
6
12
-1
12
6
4
/    Eine Raute
Ein Programm liest die „Höhe“ einer Raute ein (dies ist eigentlich die Höhe der oberen Hälfte) und gibt eine entsprechende Raute mit Sternchen aus.
Eine Raute
Hoehe? 
4


*** 
***** 
******* 
***** 
*** 
*

Allozierter Speicher

Der Aufruf von »malloc( size )« ergibt einen Zeiger auf allozierten Speicher der Größe »size« (Diese Erklärung wurde vorläufig etwas vereinfacht).

malloc
#include <stdlib.h>
void * malloc( size_t size );

Der Zeigertyp »void *« ist ein „neutraler Zeigertyp“, der in jeden anderen Typ gewandelt werden kann. Anders als andere Zeigertypen zeigen seine Werte nicht auf Objekte bestimmter Größe.

Da eine explizite Typwandlung bei »void *« nicht nötig ist und sie bestimmte Fehler verdecken kann, gilt sie unter Kennern als schlechter Stil, ist aber doch weitverbreitet, vielleicht auch wegen des Einflusses von C++, wo dies wieder anders ist.

malloc, guter Stil in C
double * p = malloc( sizeof( double ));
malloc, schlechter Stil in C
double * p =( double * )malloc( sizeof( double ));

Anwendungsgebiete Speicher wird alloziert, wenn die Größe des benötigten Speichers zur Schreibzeit noch nicht bekannt ist, oder wenn zur Schreibzeit noch nicht bekannt ist, ob dieser Speicher überhaupt benötigt wird.

/    Präsenzübung
Ein Programm soll die prozentualen Anteil von Stimmen einer Wahl ausgeben. Da die Anzahl der Parteien zur Schreibzeit noch nicht bekannt ist, wird sie vom Benutzer eingelesen.
Anzahl der Parteien? 3
100
200
300
Prozentuale Anteile:
16.6667
33.3333
50
Zusatzaufgabe Verschönerung der Ausgabe, so daß stets zwei Nachkommastellen erscheinen, gleiche Dezimalstellen immer gleiche Spalten haben und jedem Prozentwert auch ein Prozentzeichen folgt.
/    Übung
Eine Variante »strdup« der Funktion »strcpy« soll einen Zeiger auf eine Kopie eines (ihr als Argument übergebenen) Textes zurückliefern, die sie zuvor in der passenden Größe alloziert hat.
/    Übung (etwas schwieriger als die vorherig, da mehr Recherchen nötig)
Eine ganze Zahl wird in einen Text gewandelt, wobei für diesen Text genau der benötigte Speicher alloziert wird. Dieser kann zunächst mit »snprintf« ermittelt werden, bevor die Wandlung dann mit »snprintf« oder »sprintf« erledigt wird.
/    Übung (Zusatzaufgabe, weniger wichtig, etwas aufwendiger)
In einer Schleife werden zu Testzwecken int-Reihungen zufälliger Größe und mit zufälligen Inhalten erzeugt und dann von der zuvor geschriebenen Sortierfunktion sortiert. Danach prüft eine weitere Funktion ob die Reihung sortiert ist und noch dieselben Werte mit derselben Häufigkeit enthält, wie die unsortierte Reihung und berichtet gegebenenfalls gefundene Fehler.

Ressourcenverwaltung: Das Scheitern von Anforderung

Das Ergebnis von »malloc« kann auch ein Nullzeiger sein, der dann nicht dereferenziert werden darf. Er zeigt ja auf kein Objekt. Es gibt zwei Möglichkeiten, wie diese Möglichkeit berücksichtigt werden kann.

Ignorieren Wenn ein Programm für den internen Gebrauch erstellt werden soll, dann kann diese Möglichkeit bei der Programmierung ignoriert werden. Damit wird bewußt ein mangelhaftes Programm geschrieben, wenn der Aufwand für korrekte Programmierung zu groß wäre. Im besten Fall kommt es dann bei der Dereferenzierung eines Nullzeigers zu einem Programmabbruch, im schlechtesten Fall zu einem fehlerhaften Ergebnis: „If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.“. — Diese Vorgehensweise kann als Ergebnis einer bewußten Entscheidung manchmal richtig sein. Im folgenden Programmbeispiel soll der Aufruf der Funktion »memset« den Speicher mit Nullwerten füllen, er wäre aber undefiniertes Verhalten, wenn p ein Nullzeiger wäre.

Allozieren einer nullgefüllten char -Reihung mit Ignorieren
void * alloc( size_t const size )
{ void * p = malloc( size );
memset( p, 0, size );
return p; }

Behandeln Wenn ein Programm für den Gebrauch durch Dritte entwickelt wird oder es wichtig ist, daß das Programm sich in vorhersehbarer Weise und zuverlässig verhält (also kein durch den Programmierer vermeidbares undefiniertes Verhalten zeigt), dann muß es durch die Programmierung sichergestellt sein, daß ein Nullzeiger nie dereferenziert wird.

Allozieren einer nullgefüllten char -Reihung mit Behandlung
void * allocate( size_t const size )
{ void * p = malloc( size );
if( p )memset( p, 0, size );
return p; }

Leider gibt es sogar neue Lehrbücher, in denen unkritisch  (also ohne diese Problematik überhaupt zu diskutieren [wenn ich nichts übersehen habe]) auf die erste Weise „Ignorieren“ programmiert wird, wie etwa “Advanced data structures ” von Peter Brass. In diesem Buch finden sich passenderweise auch gleich die berüchtigten cast -Operatoren vor malloc -Aufrufen, »sizeof( char )«, »(int)'\0'« und »get_node()« in einer Deklaration (»http://www-cs.ccny.cuny.edu/~peter/dstest/ar_n_trie.c«). Teilweise ist eine ganze Generation von C -Programmierern „durch C++  verdorben“ worden – es ist nicht grundsätzlich falsch zwei (ähnliche) Sprachen zu erlernen, aber man muß dafür ungefähr das Doppelte der Zeit aufwenden, die man zum Lernen einer Sprache alleine einsetzen würde und darf nicht glauben, daß man mit der einen nun die andere schon fast kennt.

Hier ist insbesondere auch der Vorstellung „programmiersprachenunabhängiger Algorithmen“ entgegenzutreten: Es ergibt sich meist schlechter C -Code, wenn zunächst in Java  „gedacht“ und dann in C  geschrieben wird (oder umgekehrt), denn in C  ist die Verwaltung von Speicher integraler Bestandteil von Funktionen zur Verarbeitung von Graphen (wie Listen oder Bäumen), in Java  hingegen nicht. Es ist in solchen Fällen nicht möglich, einmal einen „sprachunabhängigen Algorithmus“ zu erlernen und diesen dann nur noch ohne weiteres Nachdenken in C  oder Java  zu formulieren.

Funktionen, die Datenverabeitungsaufgaben haben, aber nicht mit der Benutzeroberfläche eines Programms zu tun haben, also beispielsweise Rechenfunktionen, wie »sin«, werden hier auch (entsprechend der MVC-Architektur) als „Model -Funktionen“ bezeichnet.

Eine Model-Funktion kann einen Fehler nicht direkt an die Benutzeroberfläche oder Umgebung melden, da sie diese nicht kennt (nicht kennen sollte).

Eine Funktionen wie die folgende ist weniger wiederverwendbar, weil sie nicht in Programmen mit einer graphischen Benutzeroberfläche eingesetzt werden kann (wo es keinen Konsole gibt, welche printf -Ausgaben anzeigt). Die Fehlermeldung würde auch kein Sinn bei Hintergrundprogrammen ohne Benutzeroberfläche ergeben, oder bei Programmen ohne einen Bediener, der die Konsolenausgaben liest.

Allozieren einer nullgefüllten char -Reihung mit Fehlermeldung
void * allocate_console( size_t const size )
{ void * p = malloc( size );
if( p )memset( p, 0, size );
else printf( "malloc ergab 0.\n" );
return p; }

Statt dessen sollte sie den Fehler an ihren Aufrufer melden. Die oben definierte allocate -Funktion erfüllt diese Anforderung, da sie, falls malloc  einen Nullzeiger zurückliefert, selber auch einen Nullzeiger zurückliefert, woraus der Aufrufer erkennen kann, daß kein Speicher erhalten werden konnte.

Berichtsprinzip Wenn ein untergeordneter Programmteil wegen einer gescheiterten Ressourcenanforderung seine Aufgabe nicht erfüllen kann, so berichtet er dies seinem Aufrufer, der dann entscheidet, wie weiter zu verfahren ist. Dieser Bericht kann beispielsweise durch den Rückgabewert erfolgen.

Abbrechen Damit eine Model-Funktion möglichst universell (als eine Bibliotheksfunktion) in verschiedenen Programmen eingesetzt werden kann, sollte sie nicht selbständig beschließen, das ganze Programm zu beenden. (Diese Entscheidung gehört nach MVC in die Controller -Schicht einer Anwendung). Jedoch kann genau das manchmal ein sinnvoller Kompromiß zwischen „Ignorieren“ und „Behandeln“ sein, weil es einerseits undefiniertes Verhalten durch den Programmabbruch vermeidet, aber andererseits die Programmierung erleichtert, weil der sofortige Abbruch die aufwendigere Weiterleitung von Fehlerwerten überflüssig macht (jedoch sind dadurch Datenverluste möglich). Bei der folgenden Funktion kann sich der Aufrufer sicher sein, daß erfolgreich Speicher alloziert wurde, falls  sie überhaupt zurückkehrt.

Allozieren einer nullgefüllten char -Reihung mit Abbrechen
void * allocate_or_abort( size_t const size )
{ void * p = malloc( size );
if( !p )abort();
memset( p, 0, size );
return p; }

Für jedes Programmierprojekt sollte eine bewußte Entscheidung über die Art der Behandlung des Scheiterns einer Laufzeitanforderung getroffen werden. Die Vorgehensweise „Behandlung “ läßt alle Optionen offen und empfiehlt sich für hochwertige Programme und Programmteile, die oft wiederverwendet werden sollen. Die Vorgehensweise „Ignorieren “ erleichtert die Programmierung und macht den Quellcode übersichtlicher, kann aber im schlimmsten Fall zu falschen Ergebnissen führen. Die Vorgehensweise „Abbrechen “ könnte in manchen Fällen ein praktikabler Kompromiß sein. Für den Rest des Intensivkurses soll in den Programmbeispiele in der Regel die Vorgehensweise „Behandlung “ verwendet werden.

Auch andere Aufrufe, die nicht immer gelingen müssen, wie das weiter oben beschriebene Einlesen mit »scanf« können als Anforderungen in diesem Sinne verstanden werden.

Ausweglose Zustände? In manchen Fällen wird die Auffassung vertreten, daß bei bestimmten Bedingungen ohnehin keine Hilfe mehr möglich ist: Wenn ein Versuch zur Ausgabe auf die Konsole scheitert, dann kann auch keine Fehlermeldung mehr ausgegeben werden: »if( 3 != printf( "abc" ))…«?. Die Prüfung des Ergebnisses von »printf« wird tatsächlich meist weggelassen. Wenn kein Speicher mehr vorhanden ist, dann wird ein Programmteil zur Fehlerbehandlung vielleicht auch daran scheitern. Doch kann bei Mangel an alloziertem Speicher immer noch ausreichend automatischer Speicher verfügbar sein.

Ressourcenverwaltung: Das Zurückgeben von Ressourcen

Ressourcen sollten zur Wiederverwendung zurückgegeben werden, sobald sie nicht mehr benötigt werden.

Allozierter Speicher kann mit »free« zurückgegeben werden. Obwohl diese Funktion auch einen Nullzeiger akzeptiert, ist es im allgemeinen (bei anderen Typen von Ressourcen, die später behandelt werden sollen) nötig, darauf zu achten, Ressourcen nur dann wieder freizugeben, wenn sie zuvor auch erhalten wurden.

Naives Allozieren, Nutzen und Zurückgeben einer Ressource
{ int * p = malloc( sizeof( int ));
*p = 7;
free( p ); }
Korrektes Allozieren, Nutzen und Zurückgeben einer Ressource
{ int * p = malloc( sizeof( int ));
if( p )
{ *p = 7;
free( p ); }}

Auch diese Aufgabe kann machmal ignoriert  werden: Wenn ein Programm während seiner gesamten Laufzeit nur wenig Speicher anfordert oder nur kurzzeitig läuft, dann ist die explizite Rückgabe des Speichers mit »free« nicht so wichtig, weil der mit »malloc« angeforderte Speicher am Ende des Programmlaufs normalerweise automatisch wieder freigegeben wird, auch wenn das Programm »free« nicht selber aufruft. Aber bei Programmen, die viel Speicherbedarf haben und die längere Zeit laufen sollen ist die möglichst frühzeitige Speicherfreigabe wichtig, damit der Speicher nicht unnötig blockiert wird. Genauso sollten Programmteile in Bibliotheken, die auch in solchen Programmen verwendbar sein sollen, diese Speicherfreigabe erledigen oder ermöglichen.

Wenn ein Programm mehrere Ressourcen anfordert, dann muß es genau diejenigen, die es erfolgreiche erhalten hat, auch wieder freigeben, aber es darf im allgemeinen nicht versuchen, Ressourcen freizugeben oder zu benutzen, die es gar nicht erfolgreich erhalten hat (»free« toleriert den Aufruf mit einem Nullzeiger, was aber bei anderen Ressourcenfreigaben nicht immer entsprechend sein muß). Die Sicherstellung dieser Regeln wird auch als „Ressourcenverwaltung“ bezeichnet. Sie ähnelt dem Umgang mit einem Buch, das einer Leihbibliothek gehört: Man darf bei der Planung nicht einfach davon ausgehen, daß man das Buch entleihen kann (weil es schon an jemand anders verliehen worden sein könnte) und man muß es nach Ablauf einer bestimmten Frist wieder zurückgeben.

Beim Anfordern vieler Ressourcen kann deren Verwaltung unübersichtlich werden. Leider kann sie den C -Programmierer in manchen Programmen ziemlich belasten, so daß die korrekte Ressourcenverwaltung manchmal die Hauptlast einer Programmieraufgabe ausmacht.

Für jedes Programmierprojekt sollten einheitliche Regeln zum Umgang mit Ressourcen festgelegt und beachtet werden.

Ein Problem bei der Durchsetzung eines einheitlichen Stils zur Verwaltung von Ressourcen können verwendete Bibliotheken von dritter Seite sein, bei denen man nicht beinflussen kann, wie sie mit Ressourcen umgehen. Dann sollte der Stil wenigstens für bestimmte Projektteile  festgelegt werden, und außerdem sollte man sich für jede Bibliothek und jeden Programmteil bewußt sein, welcher Stil zum Umgang mit Ressourcen dort gepflegt wird.

Ressourcenverwaltung: Fingerzeige für die Wahl von Regeln

[Neu]

In einem „Hauptprogramm“ reicht es den für eine jeweilige Situation als angemessenen Aufwand zu betreiben. Beispielsweise könnte ein Programm beim Scheitern einer Anforderung einfach abgebrochen werden.

Bestimmte Ressourcen werden am Programmende automatisch freigegeben: Dies gilt für geöffnete Datei und meistens auch für allozierten Speicher. Es gibt jedoch auch Ressourcen, die bei Programmende nicht automatisch freigegeben  werden (etwa Ressourcen, die von anderen Prozessen erhalten wurden, weil diese Prozesse ja noch weiterlaufen). Sie müssen dann auf jeden Fall ordentlich verwaltet werden. Da dies nicht mehr möglich ist, wenn das Programm an einigen Stellen abgebrochen wird, empfiehlt sich dann eine ordentliche Buchführung und Verwaltung von Ressourcen. Das Offenhalten dieser Möglichkeit von Anfang an kann eine Motivation dafür sein, die Ressourcenverwaltung gleich ordentlich zu programmieren, auch wenn dies (noch) nicht als nötig erscheint.

Funktionen, die in unbekannten Kontexten wiederverwendet werden können sollen, sollten möglichst „regelneutral“ sein, also dem Klienten keine Regeln aufzwingen. Sie dürfen Fehler also weder ignorieren noch das Programm abbrechen, sondern sollten Transaktionen sein (die entweder alle nötigen Ressourcen, die einen Aufruf überdauern sollen, beschaft haben oder keine), die den Klienten bei ihrem Scheitern nur informieren.

Dateizugriffe

Inhalt des 7. Termins (2009-12-16)

Das Schreiben einer Funktion zum Kopieren wird etwas später wieder aufgegriffen werden.

Nachtrag zu Ressourcen

resource 
„Ressource“
model 
„Modell“
controller 
„Steuerung“

Übungsaufgaben

/    Zur Vertiefung von Anweisungen und Ausdrücken:
void pr( int const x )
{ if( x == 2 )printf( "a\n" );
if( x == 4 )printf( "b\n" );
else printf( "c\n" ); }
Die oben definierte Funktion »pr« soll die Zeile »a« ausgeben, wenn x gleich 2 ist; die Zeile »b«, falls x gleich 4 ist; und die Zeile »c« sonst.
Welcher Fehler wurde gemacht?
Nachdem dieser Fehler korrigiert ist:
Können Sie eine Definition einer Funktion, die dasselbe Verhalten zeigt, aber die Wiederholung von »x« vermeidet, schreiben?
Können Sie eine Definition einer Funktion, die dasselbe Verhalten zeigt, aber die Wiederholung von »printf« vermeidet, schreiben (Hierbei darf »x« dann wieder wiederholt werden)?
/    Präsenzübung, Lesen der Definition einer »strcpy«-ähnlichen Funktion
#include <stdio.h>

void strcpy_( char * p, char const * q ){ while( *p++ = *q++ ); }

int main( void )
{ char target[ 4 ]; char const * const source = "abc";
strcpy_( target, source );
printf( "%.4s\n", target ); }
Da die Namen der Standardbibliotheksfunktionen reserviert sind, wurde ein etwas anderer Name als »strcpy« verwendet.
Können Sie erklären, wieso die Definition dieser Funktion eine Kette kopieren kann?
Eine alternative Möglichkeit zur Definition (nur C99):
void strcpy_( char * restrict p, char const * restrict q ){ while( *p++ = *q++ ); }
Die Verwendung von »restrict« in obiger Deklaration bedeutet, daß bei jeder Ausführung dieser Funktion auf einen Textblock, auf den durch einen der beiden Parameter zugegriffen wird, nicht auch durch den anderen Parameter zugegriffen wird. Damit kann »strcpy« nicht verwendet werden, um überlappende Objekte zu kopieren, aber der Compiler kann bestimmte Optimierungen durchführen.
Ein Programm mit »restrict« arbeitet auch weiterhin korrekt, wenn »restrict« überall entfernt wird. Es dient nur der Optimierung. Daher ist ein aktiver Gebrauch von »restrict« nicht nötig, um korrekte C -Programme schreiben zu können. Da dieses Schlüsselwort aber in der Dokumentation verwendeter Funktionen vorkommen kann, sollte es zumindest bekannt sein.
Warum könnte das Kopieren einander überlappender Objekte mit »strcpy_« (auch ohne Verwendung von »restrict«) Schwierigkeiten bereiten?
/    Präsenzübung, Schreiben der Definition einer »strcmp«-ähnlichen Funktion
Es soll eine (etwas vereinfachte) Variante der Funktion »strcmp« definiert werden, die genau dann 0 ergibt, wenn die zu vergleichenden Texte gleich sind.
/    Berühmte Themen der C -Programmierung: Duff's Device  (Zusatzaufgabe)
Bereiten Sie eine kurze mündliche Erklärung der Bedeutung des Begriffs “Duff's Device ” vor.

VLAs (nur C99)

Bei der Einführung der Funktion »malloc« wurde als Motivation erklärt, daß damit Speicherbereiche angefordert werden können, deren Größe erst zur Laufzeit bekannt ist. Tatsächlich ist dies seit der aktuellen C -Version dann nicht unbedingt nötig, da sie mit den “variable length arrays ” Reihungen bietet, deren Größe erst zur Laufzeit festgelegt wird. Jedoch gibt es weiterhin noch genug Fälle, in denen malloc -Aufrufe nötig sind:

Dateizugriffe

/    Einfaches Kopieren
Schreiben Sie ein Programm das solange wie möglich Zeichen von stdin nach stdout kopiert. (Das Kopieren von Zeichen ist dann nicht mehr möglich, wenn das Ende [»EOF«] des Eingabestroms erreicht wurde.)
Nennen Sie diese Programm »copy« und benutzen sie es zum Kopieren von einer Datei in eine andere Datei, zum Schreiben der Tastatureingaben in eine Datei, zum Weiterleiten der Tastatureingaben in das Programm »cat -n« und zum Ausgeben des Inhalt eines Datei.
Ändern Sie das Programm so ab, daß es nur Buchstaben kopiert (alle anderen Zeichen werden sozusagen „gelöscht“).
Verwenden Sie das Programm dann in vim , um die Buchstaben in einem bestimmten Zeilenbereich zu extrahieren.

Weitere Inhalte

Formen der Interaktion zwischen Prozessen

Schon behandelt wurde das Umlenken von »stdin« und »stdout« in Kommandozeilen.

Die Ausgabe in Gravis eingeschlossener Kommandos wird in Kommandozeilen eingefügt.

Konsole
$ date
Sa 19. Dez 19:01:11 CET 2009 $ echo alpha
alpha $ echo `date`
Sa 19. Dez 19:01:21 CET 2009 $ echo `echo alpha`
alpha $ gcc -S test.c $ ls -rt | tail -1
test.s $ head -1 `ls -rt | tail -1`
.file "test.c"

Ein Programm kann einen ganzzahligen Kennwert zurückgeben, der bei erfolgreicher Ausführung normalerweise 0 ist. Dieser kann dann ignoriert, angezeigt oder in Skripten verwendet werden.

sh
$ echo alpha >tmp

$ grep alpha tmp >/dev/null
$ echo $?
0 $ grep beta tmp >/dev/null
$ echo $?
1
csh
% echo $status
1
Windows 9X
C:\>command /Z

Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1999.
Rückgabecode (ERRORLEVEL): 0
ACHTUNG: COMMAND.COM vorübergehend neu geladen
C:\>echo alpha >tmp C:\>grep alpha tmp >NUL
Rückgabecode (ERRORLEVEL): 0 C:\>grep beta tmp >NUL
Rückgabecode (ERRORLEVEL): 1 C:\>exit

Perl  hat als Skriptsprache den Vorteil, unter allen Linux -Kommandozeileninterpretierern und auch unter Windows  gleich zu sein.

Perl
use strict;
use warnings; print `grep -q alpha tmp`;
print $? >> 8, "\n"; print `grep -q beta tmp`;
print $? >> 8, "\n"; print system( "grep -q alpha tmp" )>> 8, "\n";
print system( "grep -q beta tmp" )>> 8, "\n";
STDOUT
0
1
0
1

Ein C -Programm kann einen Ausgangskennwert (“exit status ”) zurückgeben, indem es diesen als Ergebnis der Funktion »main« zurückgibt. Um portabel Erfolg oder Scheitern zu signalisieren können dafür zwei Makros »EXIT_SUCCESS« und »EXIT_FAILURE« durch »#include <stdlib.h>« definiert werden. Ein Programm kann auch mit »exit« verlassen werden, was aber etwas schlechterer Stil wäre. Wird ein Programm mit »abort« verlassen wird ein Fehlerwert zurückgegeben, der Scheitern anzeigt.

Beispiel
#include <stdlib.h> /* abort, exit, EXIT_SUCCESS, EXIT_FAILURE */

int main( void )
{ abort();
exit( EXIT_FAILURE );
return EXIT_SUCCESS; }

Auch in C  selber kann der Wert eines Prozesses ermittelt werden.

C
#include <stdlib.h> /* system, EXIT_SUCCESS */
#include <stdio.h> /* printf */ int main( void )
{ printf( "%d\n", system( "grep -q alpha tmp" )>> 8 );
printf( "%d\n", system( "grep -q beta tmp" )>> 8 );
return EXIT_SUCCESS; }
stdout
0
1

»system«

Hinweis Alle Beschreibungen von Eigenschaften der Programmiersprache C, insbesondere auch von Funktionen und anderen Standardentitäten, können in diesem Text vereinfacht worden sein. Eine ausführlichere und genauere Beschreibung findet sich ja in der C -Sprachspezifikation.

Synopsis
#include <stdlib.h>
int system(const char *string);
Wirkung Übergibt des an einen implementationsspezifischen Kommandointerpretierer.
Wert Ein implementationsspezifizierter Wert.
Beispiel »sytem( "alpha" );« gibt den Text »alpha« aus.

Kommandozeilenargumente

int main( int argc, char *argv[] ){ /* ... */ }

argc Gibt die Anzahl vorhandener Kommandozeilenargumente an, falls positiv enthalten diese Programmnamen (»argv[ 0 ]«) sowie Kommandozeilenargumente (»argv[ 1 ]« bis »argv[ argc - 1 ]«)

»argv« ist als Zeiger „ein Zeiger auf einen Zeiger“, jedoch kann man diese Komplikation durch „ein Zeiger auf eine Kette“ anschaulicher formulieren. Für einen Anfänger erscheinen „Zeiger auf Zeiger auf Zeiger …“ vielleicht als verwirrend, wenn man sich einmal an sie gewöhnt hat, dann sind sie aber nicht schwieriger zu verstehen als beispielsweise Unterverzeichnisse („Verzeichnisse von Verzeichnissen“).

Es gilt laut ISO/IEC 9899:1999 (E)
»argc >= 0«
»argv[ argc ]« ist ein Nullzeiger.
/    Einfacher „Taschenrechner“
Ein Programm soll die Summe der angegebenen Argumente ausgeben.
./a.out 12 34 56

102
Zum Umwandeln der Ketten in int -Werte ist die Funktion »sscanf« nützlich.
#include <stdio.h>
/* ... */ int i; sscanf( "123", "%d", &i );

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722303 stefan_ram:722303 Erster Teil des Intensivkurses C Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722303, slrprddef722303, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten.
https://www.purl.org/stefan_ram/pub/erster_teil_des_intensivkurses_c