1 Theorie | |
→ | 1.1 Die Programmiersprache C |
Die Funktion printf() — print formatted — dient zur
formatierten Ausgabe auf die Standardausgabe.
Formatiert bedeutet hier, dass feststehender Text mit Variablen und
Berechnungstermen kombiniert wird und das Aussehen der Ausgabe
weitgehend beeinflusst werden kann.
Die Funktion hat den Prototypen
int printf(const char *format, ...);
Als erstes Argument muss ein Format-String angegeben werden. Ob weitere Argumente folgen, hängt vom Inhalt des Formatstrings ab.
Beispiel:
printf("Seitenlaenge: %lg -> Flaecheninhalt: %lg\n", slaenge, flaeche);
bewirkt eine Ausgabe wie z.B.:
Seitenlaenge: 2.5 -> Flaecheninhalt: 6.25
Der Format-String besteht aus konstantem Text und Platzhaltern für die auszugebenden Werte. Die auszugebenden Werte selbst folgen als weitere Argumente auf den Formatstring.
Die Reihenfolge der Platzhalter im Format-String muss der Reihenfolge der weiteren Argumente entsprechen.
Die Platzhalter werden hier nur in einer kurzen Zusammenfassung vorgestellt, zu Details (z.B. weiteren Kennbuchstaben für Datentypen) kann die Online-Hilfe konsultiert werden.
Ein Platzhalter besteht aus folgenden Komponenten:
- | Text linksbündig in der angegebenen Feldbreite platzieren (Standard ist rechtsbündig) |
0 | Numerische Werte mit führenden Nullen auffüllen, um Mindestfeldbreite auszufüllen |
+ | für vorzeichenbehaftete Datentypen das Vorzeichen auch für nichtnegative Werte ausgeben |
Leerzeichen | Anstelle eines Vorzeichens ein Leerzeichen für positive numerische Werte ausgeben |
# |
|
c | char (einzelnes Zeichen) |
s | String (0-Byte-terminierte Zeichenkette) |
d i |
int, Ausgabe dezimal |
u | unsigned, Ausgabe dezimal |
o | unsigned, Ausgabe oktal |
x X |
unsigned, Ausgabe hexadezimal, entweder mit "abcdef" (x) oder "ABCDEF" (X) |
e E |
float, Ausgabe immer mit Exponent, Exponent mit kleinem (e) oder großem (E) e abgetrennt |
f | float, Ausgabe ohne Exponenten |
g G |
float, automatische Auswahl ob mit oder ohne Exponent. Groß- und Kleinschreibung entscheidet über großes oder kleines e bei Ausgabe mit Expontenten |
Flags, Breite und Präzision werden eigentlich nur benötigt, wenn eine tabellarische Darstellung erzeugt werden soll oder ein älteres Dateiformat eine bestimmte Formatierung erfordert.
Datentyp | Platzhalter bei Ausgabe mit printf-Familie |
Platzhalter bei Eingabe mit scanf-Familie |
Anmerkungen |
---|---|---|---|
double | %g | %lg | optimale Darstellung (mit bzw. ohne Exponenten) |
%e | %le | immer mit Exponenten | |
%f | %lf | immer ohne Exponenten | |
int | %d | %d | dezimal |
unsigned | %u | %u | dezimal |
%x | %x | hexadezimal mit a-f | |
%X | %X | hexadezimal mit A-F | |
%o | %o | oktal | |
long int (long) | %ld | %ld | dezimal |
long unsigned | %lu | %lu | dezimal |
%lx | %lx | hexadezimal mit a-f | |
%lX | %lX | hexadezimal mit A-F | |
%lo | %lo | oktal | |
intmax_t | %jd | %jd | nicht unter Windows |
%I64d oder %lld | %I64d oder %lld | nur unter Windows | |
uintmax_t | %ju | %ju | nicht unter Windows |
%I64u oder %llu | %I64u oder %llu | nur unter Windows | |
%jx | %jx | hexadezimal mit a-f, nicht unter Windows | |
%I64x oder %llx | %I64x oder %llx | hexadezimal mit a-f, nur unter Windows | |
%jX | %jX | hexadezimal mit A-F, nicht unter Windows | |
%I64X oder %llX | %I64X oder %llX | hexadezimal mit A-F, nur unter Windows | |
%jo | %jo | oktal, nicht unter Windows | |
%I64o oder %llo | %I64o oder %llo | oktal, nur unter Windows | |
char *s (0-Byte-terminierter String) |
%s | %s | |
char | %c | %c |
Unter Windows sind die Standard-Platzhalter %jd und %ju für
intmax_t und uintmax_t nicht verfügbar. Deshalb müssen behelfsweise
Platzhalter für 64-Bit-Datentypen verwendet werden.
Beim Aufkommen neuer Prozessorgenerationen mit mehr als 64 Bit
breiten Datentypen darf dann der gesamte Quelltext nach %I64 bzw.
%ll durchsucht werden. Es muss dann an jeder Stelle einzeln
entschieden werden, ob wirklich ein 64-Bit-Wert ausgegeben werden
soll oder eine Zahl mit maximaler Bitbreite. In letzterem Fall muss
der Platzhalter dann geändert werden.
Code für die Ausgabe von intmax_t bzw. uintmax_t sollte gekapselt werden. In einem eigenen Quelltextmodul könnte man Funktionen wie
int intmax_t_to_file(FILE *output_file, intmax_t value); int uintmax_t_to_file(FILE *output_file, uintmax_t value);
schreiben. Alle Ausgaben von intmax_t und uintmax_t in Datei
werden dann über diese Funktionen getätigt.
Beim Umstieg auf Prozessoren mit größerer Bitbreite müssten dann
nur diese beiden Funktionen angepasst werden.
Das Programm ex078.c
verdeutlicht die Verwendung verschiedener Platzhalter.
Die Ausgabe
Ausgabe der int-Werte Ausgabe mit ...%d... ...%d... ...-1... ...10000... Ausgabe mit ...%4d... ...%4d... ... -1... ...10000... Ausgabe mit ...%6d... ...%6d... ... -1... ... 10000... Ausgabe mit ...%06d... ...%06d... ...-00001... ...010000... Ausgabe mit ...%+6d... ...%+6d... ... -1... ...+10000... Ausgabe mit ...%+06d... ...%+06d... ...-00001... ...+10000... Ausgabe mit ...%-6d... ...%-6d... ...-1 ... ...10000 ... Ausgabe der double-Werte Ausgabe mit ...%e... ...%e... ...-1.414214e+00... ...1.414214e+10... Ausgabe mit ...%f... ...%f... ...-1.414214... ...14142135623.730951... Ausgabe mit ...%g... ...%g... ...-1.41421... ...1.41421e+10... Ausgabe mit ...%.3e... ...%.3e... ...-1.414e+00... ...1.414e+10... Ausgabe mit ...%.3f... ...%.3f... ...-1.414... ...14142135623.731... Ausgabe mit ...%.3g... ...%.3g... ...-1.41... ...1.41e+10...
verdeutlicht folgende Sachverhalte:
Analog zur Ausgabe mit printf() funktioniert die Eingabe mit scanf(). Die Funktion liest Daten von der Standardeingabe.
Auch hier ist das erste Argument ein Formatstring mit
Platzhaltern. Die Platzhalter geben an, welche Datentypen in
welcher Reihenfolge eingelesen werden sollen.
Nach dem Formatstring folgen als weitere Argumente die
Adressen im Hauptspeicher, an denen die eingelesenen Werte
gespeichert werden sollen. Häufig wird hierfür mit dem
Adressoperator & die Adresse einer zu befüllenden
Variable gebildet.
Die Reihenfolge der Platzhalter im Format-String muss der
Reihenfolge der weiteren Argumente entsprechen.
Die zu scanf() ähnlichen Funktionen sscanf() und fscanf() dienen dazu, Werte nicht von der Standardeingabe sondern aus einem vorhandenen String oder einer anderen Datei zu lesen.
Das Ergebnis der Funktionen scanf(), sscanf() und fscanf() ist ein ganzzahliger Wert. Dieser gibt an, wieviele der gewünschten Eingaben bzw. Konvertierungen erfolgreich durchgeführt wurden. Bei vollem Erfolg ist der Rückgabewert gleich der Anzahl der Platzhalter im Format-String.
Die Funktionen scanf() und fscanf() arbeiten
gepuffert. Sie erhalten erst dann Text zur Verarbeitung, wenn die
Eingabezeile mit ENTER abgeschlossen ist. Somit steht dann am Ende
des Eingabepuffers ein Newline '\n'.
Dieses Newline wird durch scanf() bzw. fscanf() nicht
aus dem Eingabepuffer entnommen. Wird anschließend z.B. mit
getchar() o.ä. ein Zeichen gelesen, wird dieses Newline
zurückgegeben.
Insbesondere die Kombination von scanf()/fscanf() mit
getchar()/fgetc() kann unerwartete Ergebnisse
liefern.
Es wird stattdessen empfohlen, mit fgets() eine Zeile einzulesen (hierbei wird das abschließende Newline aus dem Eingabepuffer entnommen und in den Zielpuffer geschrieben) und dann mit sscanf() die gewünschten Werte aus der Textzeile zu extrahieren.
Anstelle von
#include <stdio.h> int main(void) { int i; scanf("%d", &i); printf("i=%d\n", i); return 0; }
wird also besser
#include <stdio.h> int main(void) { char buffer[256]; int i; if (fgets(buffer, sizeof(buffer), stdin)) { if (1 == sscanf(buffer, "%d", &i)) { printf("i=%d\n", i); } } return 0; }
benutzt.
Die Funktion
int sscanf(const char *str, const char *format, ...);
liest Daten aus dem Textpuffer str, ohne diesen zu
ändern. Der Textpuffer kann z.B. vorher durch einen Aufruf von
fgets() mit Daten aus einer Datei oder aus der
Standardeingabe gefüllt worden sein.
Der Formatstring mit Platzhaltern gibt auch hier an, welche
Datentypen in welcher Reihenfolge gelesen werden sollen. Nach dem
Formatstring folgen als weitere Argumente die Adressen im
Hauptspeicher, an denen die eingelesenen Werte gespeichert werden
sollen. Häufig wird hierfür mit dem Adressoperator & die
Adresse einer zu befüllenden Variable gebildet.
Die Reihenfolge der Platzhalter im Format-String muss der
Reihenfolge der Adressen entsprechen.
Die Funktion liefert als Rückgabewert die Anzahl erfolgreich
abgearbeiteter Platzhalter.
Wird in einem C-Programm die Funktion printf() oder
verwandte Funktionen wie fprintf() oder sprintf()
verwendet, werden viele Bibliotheksmodule zum Programm
hinzugelinkt. Die Flexibilität von printf() muss ja
schließlich in Code realisiert sein.
Dies gilt in analoger Weise für die Eingabe mit
scanf()/fscanf()/sscanf().
Auf modernen Desktop- und Server-Systemen stellt dies normalerweise
kein Problem dar.
Auf Mikrocontrollern ist jedoch der verfügbare Speicher begrenzt.
Die Funktionsfamilien printf() und scanf() können
damit in vielen Fällen nicht verwendet werden.
Für Ausgaben kann ein String-Puffer mit den auszugebenden Daten vorbereitet werden, mit
fputs(puffer, stdout);
wird dann die Ausgabe des Puffers veranlasst.
Die Funktion
int fputs(const char *s, FILE *stream);
gibt einen String in eine zum Schreiben geöffnete Datei aus. Das Lesen und Schreiben mit FILE-Strukturen wird später noch behandelt.
Die Beispiel-Dateien he-ulongso.h und he-ulongso.c enthalten die Funktion
int hsm_et_ulong_to_string(char *rb, size_t sz, unsigned long ul, int *ec);
Die Funktion konvertiert den unsigned-long-Wert ul in
einen Puffer, dessen Adresse rb angibt und dessen Größe in
sz angegeben ist.
Die Funktion
int hsm_et_ulong_to_file(FILE *of, unsigned long ul, const char *fn, int *ec);
aus he-ulongfo.h bzw. he-ulongfo.c benutzt hsm_et_ulong_to_string() um einen unsigned-long-Wert erst zu konvertieren und dann in eine Datei auszugeben.
Analog stellen die Dateien he-longso.h, he-longso.c, he-longfo.h und he-longfo.c die Funktionen
int hsm_et_long_to_string(char *rb, size_t sz, long ul, int *ec); int hsm_et_long_to_file(FILE *of, long ul, const char *fn, int *ec);
bereit, um einen long-Wert in einen char-Puffer zu konvertieren und dann in Datei auszugeben.
Auch kleinere Datentypen (short, int, unsigned short, unsigned)
können mit diesen Funktionen ausgegeben werden, dabei werden short
und int nach long konvertiert und ausgegeben, unsigned short und
unsigned nach unsigned long.
Beispiel:
int x; ... hsm_et_long_to_file(of, (long)x, "testfile.txt", NULL);
Die zeilenweise Verarbeitung von Text-Dateien bzw. Texteingaben kann mit fgets() erfolgen. Damit wird eine Textzeile einschließlich des Newlines für das Zeilenende in einen Puffer eingelesen. Der Puffer wird mit einem Nullbyte abgeschlossen, so dass die String-Funktionen benutzt werden können.
Sollen dem Puffer Textworte entnommen werden, kann strtok() bzw. strtok_r() benutzt werden, um den Puffer in Textworte aufzusplitten.
Sollen dem Puffer Zahlenwerte entnommen werden, stehen die Funktionen strtoumax() (Lesen einer uintmax_t-Zahl), strtoimax() (Lesen einer intmax_t-Zahl), strtoul() (Lesen eines unsigned long), strtol() (Lesen einer long-Zahl) und strtod() (Lesen einer double-Zahl) zur Verfügung.
Quelle: CERT C Coding Standard 〈1〉
1 | http://www.securecoding.cert.org |