Menu

printf in C: Formatzeichenketten, Formatangaben und Gleitkommazahlen ausgeben

Wie printf in C tatsächlich funktioniert - das Modell der Formatzeichenkette, die Angaben, die du täglich benutzt, Breite und Genauigkeit für ausgerichtete Ausgabe, Gleitkommazahlen vernünftig ausgeben, der Rückgabewert, und warum printf(user_input) ein Sicherheitsloch ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

printf ist die erste Funktion, die jeder C-Programmierer lernt, und die, für die die meisten nie das Handbuch ganz lesen. Zehn Minuten lohnen sich, denn ihre Formatzeichenkette ist eine kleine Sprache für sich - und eine Angabe falsch zu machen ist kein Tippfehler, es ist undefiniertes Verhalten.

Sie lebt in <stdio.h>, jedes Programm, das sie benutzt, beginnt also mit #include <stdio.h>.

Das Modell der Formatzeichenkette

printf läuft die Formatzeichenkette Zeichen für Zeichen ab. Alles, was kein % ist, wird genau so ausgegeben, wie es dasteht. Ein % beginnt eine Umwandlungsangabe, die das nächste Argument verbraucht und in der gewünschten Form ausgibt.

Die Argumente werden den Angaben streng nach Position zugeordnet: Das erste % nimmt count, das zweite name und so weiter. printf hat keine Möglichkeit zu prüfen, ob du übergeben hast, was du versprachst - es kann die Typen nicht sehen -, die Formatzeichenkette ist also ein Vertrag, den du einhalten musst. Deshalb lohnt es sich, -Wall einzuschalten: GCC liest die wörtliche Formatzeichenkette zur Übersetzungszeit und warnt, wenn ein Argument nicht passt.

Nichts erzwingt einen Zeilenumbruch. printf("Hallo") lässt den Cursor hinter dem o stehen, weshalb fast jede Formatzeichenkette mit \n endet.

Die Angaben, die du täglich benutzt

Fünf decken den meisten Code ab:

AngabeGibt ausBeispielaufrufAusgabe
%dein vorzeichenbehaftetes intprintf("%d", -42)-42
%fein doubleprintf("%f", 2.5)2.500000
%seine Zeichenkette (char *)printf("%s", "hi")hi
%cein einzelnes Zeichenprintf("%c", 'A')A
%peine Zeigeradresseprintf("%p", (void*)&x)0x7ffd...

Zwei Regeln verstecken sich in diesem Ausschnitt. Ein wörtliches % wird als %% geschrieben - ein einzelnes % am Ende einer Formatzeichenkette ist undefiniertes Verhalten. Und %p verlangt ein void *, caste den Zeiger also; ein int * direkt zu übergeben ist streng genommen undefiniert, auch wenn es in der Praxis sauber ausgegeben wird.

Die vollständige Tabelle - jede Angabe, jeder Längenmodifikator, die scanf-Spalte - steht auf der Seite Formatangaben. Diese Seite bleibt bei denen, zu denen du greifst.

Breite, Genauigkeit und Ausrichtung

Zwischen das % und den Buchstaben kannst du Flags, eine Breite und eine Genauigkeit setzen. So machst du aus einer Schleife eine Tabelle.

Die Breite ist ein Minimum, nie ein Maximum: %3d mit dem Wert 123456 gibt alle sechs Ziffern aus. Die Genauigkeit bedeutet je Typ etwas anderes - Stellen nach dem Komma bei %f, maximale Zeichenzahl bei %s, Mindestzahl an Ziffern bei %d.

Beide lassen sich zur Laufzeit mit einem * liefern, das ein int-Argument liest:

width zu ändern legt die ganze Tabelle neu aus. Das ist die übliche Art, in C ausgerichtete Spalten auszugeben - kein Hilfsmittel zum Auffüllen von Zeichenketten nötig.

Gleitkommazahlen vernünftig ausgeben

%f ohne Genauigkeit gibt immer sechs Stellen nach dem Komma aus, was selten jemand will:

Drei Möglichkeiten und je eine Faustregel:

  • %.Nf für Geld, Messwerte, alles mit einer natürlichen Anzahl von Nachkommastellen. %.2f ist das, was du für Währungen willst.
  • %e für wissenschaftliche Notation, immer d.dddddde±dd.
  • %g, wenn du die Größenordnung nicht im Voraus kennst. Es wählt %f oder %e, je nachdem was kürzer ist, und entfernt nachlaufende Nullen - die vernünftigste Voreinstellung, um einen Wert zu protokollieren, den du noch nicht gesehen hast.

Eine Falle, klar gesagt: Es gibt kein %lf in printf. float-Argumente werden zu double befördert, bevor printf sie je sieht, %f behandelt also beide, und %lf ist nur in scanf bedeutsam, wo die Größe des Ziels zählt. (C99 akzeptiert %lf in printf als Synonym, aber die Gewohnheit, es zu schreiben, verleitet dazu, in scanf %f zu schreiben, und das geht wirklich kaputt.)

Ganzzahlen anderer Breiten

%d ist für int. Größere oder vorzeichenlose Typen brauchen einen Längenmodifikator, und sie zu verwechseln ist undefiniertes Verhalten statt eines Rundungsfehlers:

%zu für size_t ist das, was die Leute am häufigsten falsch machen - sizeof liefert size_t, nicht int, printf("%d", sizeof x) ist also ein Bug, der auf 32-Bit-Systemen zufällig funktioniert und auf 64-Bit-Systemen spinnt.

Der Rückgabewert

printf liefert die Anzahl der geschriebenen Zeichen oder eine negative Zahl bei einem Fehlschlag. Er wird meist ignoriert, und das ist überwiegend in Ordnung - aber er ist wirklich nützlich, wenn du wissen musst, wie breit die Ausgabe war:

Die verwandten sprintf und snprintf schreiben in einen Puffer statt auf den Bildschirm und liefern die erzeugte Länge. Bevorzuge immer snprintf, das die Puffergröße entgegennimmt und nicht überlaufen kann:

snprintf liefert die Länge, die die vollständige Zeichenkette gebraucht hätte, ein Rückgabewert ab der Puffergröße sagt dir also, dass die Ausgabe abgeschnitten wurde.

Die Formatzeichenketten-Schwachstelle

Das ist das eine Sicherheitsproblem bei printf, und es ist schwerwiegend. Übergib nie Daten, die du nicht selbst geschrieben hast, als Formatzeichenkette:

/* GEFÄHRLICH - tu das nie */
printf(user_input);

/* Korrekt */
printf("%s", user_input);

Enthält user_input ein %s, liest printf ein nie übergebenes Argument und folgt dem zeigerförmigen Müll, den es findet, und stürzt meist ab. Wiederholtes %x kippt Stackinhalte auf den Bildschirm, darunter womöglich Passwörter oder Schlüssel. Und %n, das die Zeichenzahl in ein Argument schreibt, hat das historisch zu beliebiger Speicherbeschädigung und Codeausführung aus der Ferne gemacht.

Die Regel ist absolut und kostet nichts: Die Formatzeichenkette ist immer ein Literal, das du geschrieben hast. Alles Veränderliche geht als Argument hinein. GCCs -Wformat-security beanstandet den Fehler, und es ist in -Wall -Wextra enthalten.

Pufferung: warum Ausgabe manchmal spät erscheint

printf schreibt nicht sofort auf das Terminal. Die Ausgabe wird gepuffert - geleert, wenn der Puffer voll ist, wenn auf einem Terminal ein Zeilenumbruch auftaucht, oder wenn das Programm normal endet. Zwei Folgen:

  • Ein Programm, das abstürzt, kann Ausgabe verlieren, die „schon ausgegeben war". Wenn du mit printf debuggst und die letzte Meldung nie erscheint, geschah der Absturz nach dieser Zeile, nicht davor. Füge fflush(stdout) hinzu oder schicke Debug-Ausgabe nach stderr, das ungepuffert ist.
  • fprintf(stderr, ...) ist die richtige Art, Fehler auszugeben. Es ist ungepuffert und geht in einen eigenen Strom, den Benutzer unabhängig von der normalen Ausgabe umleiten können.

Häufig gestellte Fragen

Wie funktioniert printf in C?

printf nimmt eine Formatzeichenkette entgegen und durchläuft sie Zeichen für Zeichen. Gewöhnliche Zeichen werden unverändert ausgegeben; jedes % beginnt eine Umwandlungsangabe, die eines der folgenden Argumente verbraucht und in der gewünschten Form ausgibt. printf("%d Aepfel\n", 5) gibt 5 Aepfel und einen Zeilenumbruch aus.

Wie gebe ich in C eine Gleitkommazahl mit 2 Nachkommastellen aus?

Benutze eine Genauigkeit: printf("%.2f\n", 3.14159) gibt 3.14 aus. Die Zahl hinter dem Punkt ist die Anzahl der Stellen nach dem Komma. %f ohne Genauigkeit gibt immer 6 Stellen aus, weshalb unformatierte Gleitkommazahlen so unruhig aussehen.

Warum gibt printf Müll aus oder stürzt ab?

Fast immer wegen einer Fehlpaarung zwischen Angabe und Argument. printf("%d", 3.5) oder printf("%s", 42) ist undefiniertes Verhalten - printf kann die echten Typen nicht sehen, es vertraut der Formatzeichenkette. Kompiliere mit -Wall, wodurch GCC das Format gegen die Argumente prüft.

Warum ist printf(user_input) gefährlich?

Enthält der Text des Benutzers %-Folgen, befolgt printf sie und liest Argumente, die nie übergeben wurden - was Stackspeicher preisgibt und mit %n sogar in den Speicher schreibt. Das ist die klassische Formatzeichenketten-Schwachstelle. Schreibe stattdessen immer printf("%s", user_input).

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S