scanf ist der Spiegel von printf: derselbe Header, ähnlich aussehende Formatzeichenketten, entgegengesetzte Richtung. Es liest Text von der Standardeingabe, wandelt ihn gemäß dem Format um und speichert die Ergebnisse über die Zeiger, die du ihm gibst.
Es ist auch die Funktion, die mehr verwirrte Anfänger erzeugt als jede andere in C, denn drei ihrer Verhaltensweisen sind überraschend und eine davon ist ein Sicherheitsloch. Diese Seite behandelt alle vier.
Die Grundform, und warum &
Tippe eine Zahl und drücke Enter, um das auszuführen. Das &age ist keine Zierde. C übergibt jedes Argument als Wert, hättest du also scanf("%d", age) geschrieben, bekäme die Funktion eine Kopie des aktuellen (Müll-)Werts von age und hätte keine Möglichkeit, die Variable selbst zu erreichen. &age übergibt die Adresse der Variablen, über die scanf dann schreibt. Siehe Zeiger für das ganze Bild.
Das & wegzulassen ist der mit Abstand häufigste scanf-Fehler. Der Compiler fängt es mit -Wall ab (format '%d' expects argument of type 'int *'); ohne eingeschaltete Warnungen kompiliert es und beschädigt Speicher an irgendeiner zufälligen Adresse.
Mehrere Werte lesen
Ein Aufruf kann mehrere Werte lesen. Leerraum in der Eingabe - Leerzeichen, Tabs, Zeilenumbrüche - trennt sie, und die numerischen Angaben überspringen beliebig viel davon.
Die wörtlichen Leerzeichen in "%d %d %d" sind eigentlich überflüssig - %d überspringt führenden Leerraum bereits -, aber sie machen das Format lesbar. Was nicht überflüssig ist, ist die Prüfung des Rückgabewerts, unten behandelt.
Zeichen im Format, die kein Leerraum sind, müssen exakt zur Eingabe passen, und so liest man strukturierte Eingaben:
Das : im Format verlangt ein : in der Eingabe. Tippe 9:30, und es wird ausgewertet; tippe 9 30, und es scheitert nach dem Lesen der 9.
Prüfe immer den Rückgabewert
scanf liefert die Anzahl der erfolgreich zugewiesenen Elemente - nicht die Anzahl der Zeichen und kein Erfolgskennzeichen. Es liefert EOF, wenn die Eingabe endete, bevor irgendetwas umgewandelt wurde.
Tippe bei der Aufforderung hallo. scanf liefert 0, n bleibt unberührt - und hält weiter den Müll, mit dem es begann - und der störende Text liegt weiterhin im Eingabestrom. Dieser letzte Teil macht aus einer einzigen schlechten Eingabe eine Endlosschleife:
/* BUG: dreht sich bei nicht-numerischer Eingabe ewig */
while (scanf("%d", &n) != 1) {
printf("Nochmal: ");
}
scanf verbraucht den Text, den es nicht umwandeln konnte, nie, der nächste Aufruf scheitert also an denselben Zeichen, für immer. Zur Erholung musst du den Rest der Zeile selbst verwerfen:
Die EOF-Prüfung ist wichtig: Wenn der Eingabestrom endet (der Benutzer drückt Strg+D, oder eine umgeleitete Datei ist zu Ende), gibt scanf danach für immer EOF zurück, eine Schleife, die nur auf != 1 testet, versucht es also ewig weiter. Behandle "ungültige Eingabe" und "keine Eingabe mehr" als die zwei verschiedenen Fälle, die sie sind. Damit lohnt sich dieser Helfer discard_line in jedem Programm, das scanf auf menschliche Eingaben anwendet.
Der %s-Pufferüberlauf
%s liest Zeichen in ein char-Array. Beachte, dass es kein & gibt - ein Arrayname zerfällt bereits zu einem Zeiger auf sein erstes Element, &name wäre also der falsche Typ.
Das Problem ist, dass ein schlichtes %s keine Ahnung hat, wie groß dein Array ist:
char name[10];
scanf("%s", name); /* GEFÄHRLICH: 40 Zeichen getippt, 41 Bytes geschrieben */
Nichts stoppt das Schreiben am Ende des Arrays. Es läuft in das hinein, was als Nächstes im Speicher liegt - andere Variablen, die Rücksprungadresse -, und das ist der klassische Pufferüberlauf, historisch die meistausgenutzte Fehlerklasse in C. Die Lösung ist eine Breite zwischen dem % und dem s, um eins kleiner als die Arraygröße, um Platz für das abschließende '\0' zu lassen:
Die Breite muss als Literal in der Formatzeichenkette stehen, was es lästig macht, sie mit der Arraygröße synchron zu halten - ein weiterer Grund, warum das meiste produktive C stattdessen fgets benutzt.
Die zweite %s-Überraschung: Es stoppt beim ersten Leerraum. Tippe Ada Lovelace, und name hält Ada; Lovelace wartet im Strom auf den nächsten Lesezugriff. %s liest ein Wort, keine Zeile.
Der Zeilenumbruch-Restbug
Das ist das „scanf funktioniert nicht", auf das jeder trifft. Numerische Angaben überspringen führenden Leerraum; %c nicht.
Und so läuft es ohne dieses Leerzeichen ab. Du tippst 30 und drückst Enter. %d verbraucht die 3 und die 0 und stoppt am Zeilenumbruch und lässt '\n' im Puffer. Das nächste scanf("%c", &initial) liest diesen Zeilenumbruch als Zeichen und kehrt sofort zurück - die Aufforderung erscheint und wird im selben Atemzug übersprungen.
Ein Leerzeichen in der scanf-Formatzeichenkette bedeutet „überspringe hier beliebig viel Leerraum", " %c" steigt also über den verirrten Zeilenumbruch und wartet auf ein echtes Zeichen. Das Leerzeichen wird nur vor %c und %[ gebraucht; jede andere Angabe überspringt Leerraum von selbst.
Gleitkommazahlen und Zeichen lesen
Die Angaben sind größtenteils dieselben wie bei printf, mit einem wichtigen Unterschied: scanf muss die genaue Größe des Ziels kennen, weil es über einen Zeiger schreibt.
Probiere 1.5 2.25 7 x.
%f liest ein float, %lf liest ein double. In printf geben beide gleich aus, weil floats vor dem Aufruf zu double befördert werden - aber in scanf gibt es keine Beförderung, und %f mit einem double * zu benutzen schreibt vier Bytes in eine Acht-Byte-Variable und lässt sie beschädigt zurück. Das %c hier braucht kein führendes Leerzeichen, weil das %d davor bereits an einem Leerzeichen gestoppt hat, nicht an einem Zeilenumbruch. (Die vollständige Tabelle steht unter Formatangaben.)
fgets: die robuste Alternative
Für alles, was eine Person tippt, lies die ganze Zeile und werte sie danach aus. fgets nimmt die Puffergröße als Argument entgegen, es kann also physisch nicht überlaufen, und wenn die Zeile hineinpasst, verbraucht es auch den Zeilenumbruch, sodass nichts zurückbleibt, was den nächsten Lesezugriff verwirrt. (Eine Zeile, die länger als der Puffer ist, lässt ihren Rest ungelesen - das fehlende '\n' im Erhaltenen ist, woran du das erkennst.)
Drei Punkte zu diesem Muster:
sizeof lineübergibt die echte Größe, das Ändern der Arraygröße braucht also keine weitere Bearbeitung - anders als beim Literal%19s.fgetsbehält den Zeilenumbruch, wenn die Zeile hineinpasste, weshalb dasstrcspn-Abschneiden da ist. So erkennst du auch eine für den Puffer zu lange Zeile: kein'\n'im Erhaltenen.- Werte danach aus.
strtolfür Ganzzahlen (es kann dir auch sagen, wo das Auswerten stoppte),strtodfür Gleitkommazahlen, odersscanf(line, "%d %d", &a, &b), um ein scanf-Format auf eine Zeichenkette anzuwenden, die du bereits sicher hältst.
sscanf verdient eine eigene Erwähnung: Es ist scanf, das aus einer Zeichenkette statt aus dem Eingabestrom liest, und es lässt sich perfekt mit fgets kombinieren. Du bekommst scanfs bequemes Auswerten ohne seine Stromprobleme.
Häufige Fehler
- Das
&vergessen bei einem Nicht-Array-Argument. Kompiliere mit-Wall. %sohne Breite. Ein Pufferüberlauf, der auf eine lange Eingabe wartet.- Den Rückgabewert ignorieren. Und dann eine Variable lesen, die
scanfnie zugewiesen hat. - Auf ein fehlgeschlagenes
scanfschleifen, ohne die Eingabe zu verwerfen. Eine Endlosschleife. %cohne führendes Leerzeichen nach dem Lesen einer Zahl. Der Bug mit der übersprungenen Aufforderung.%ffür eindoublebenutzen. Stille Beschädigung;scanfbraucht%lf.- Erwarten, dass
%seine Zeile liest. Es liest ein durch Leerraum begrenztes Wort.
Häufig gestellte Fragen
Warum braucht scanf in C ein &?
Weil C Argumente als Wert übergibt. scanf muss deine Variable ändern, es braucht also deren Adresse und keine Kopie ihres Werts: scanf("%d", &age). Die eine Ausnahme ist ein Zeichenarray, dessen Name bereits eine Adresse ist - scanf("%19s", name) bekommt kein &.
Warum überspringt scanf meine Eingabe für ein char oder eine Zeichenkette?
Ein vorheriges scanf("%d", ...) hat den Zeilenumbruch der Eingabetaste im Eingabepuffer liegen lassen. %d und %f überspringen führenden Leerraum, %c nicht, es liest also diesen Zeilenumbruch, statt zu warten. Die Lösung ist ein Leerzeichen im Format - scanf(" %c", &ch) -, das scanf sagt, zuerst Leerraum zu überspringen.
Wie liest man in C sicher eine Zeichenkette mit scanf?
Gib %s eine maximale Breite, die um eins kleiner ist als dein Puffer: char name[20]; scanf("%19s", name);. Ohne die Breite schreibt %s so viele Zeichen, wie der Benutzer tippt, und läuft bereitwillig über das Ende des Arrays hinaus. Denk auch daran, dass %s beim ersten Leerraum stoppt und daher keinen vollen Namen mit Leerzeichen lesen kann.
Sollte ich in C scanf oder fgets benutzen?
Nimm fgets für alles, was ein Mensch tippt. Es liest eine ganze Zeile in einen Puffer bekannter Größe, kann nicht überlaufen und verbraucht (wenn die Zeile hineinpasst) auch den Zeilenumbruch, die klassischen Fehler bei gemischten Eingaben verschwinden also. Werte die Zeile danach mit sscanf oder strtol aus. scanf ist für kleine Übungen und wohlgeformte Maschineneingaben in Ordnung.