Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (2024)

Um auf den nächsten Seiten etwas mehr mit den Programmen machen zu können, benötigen Sie Kenntnisse in der einfacheren (hier formatierten) Ein- und Ausgabe. Die in diesem Kapitel vorgestellten Funktionen printf() und scanf() sind recht gut für diesen Einstieg geeignet, stellen allerdings keinesfalls das Nonplusultra in C dar. Ganz im Gegenteil: Beide Funktionen sind eher als sicherheitskritisch zu betrachten und in der Praxis mit Vorsicht zu genießen (Stichwort: »Format String Exploits«).

4.1 Formatierte Eingabe mit »scanf()« Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (3)


Hinweis

Zuerst noch ein Hinweis für die absoluten Anfänger in der Programmiersprache C. Einiges wird Ihnen in diesem Kapitel ein wenig unklar sein. Aber wenn Sie das Buch ganz durchgelesen haben, wird sich vieles von selbst klären. Sie werden Erleuchtung finden, versprochen!

Betrachten wir zunächst die Syntax dieser Funktion:

//benötigter include für diesen Befehl#include <stdio.h>int scanf(const char * restric format, ...);

Mit der Funktion scanf() können Werte unterschiedlicher Datentypen formatiert eingelesen werden. Eingelesen wird dabei von der Standardeingabe (stdin). Mit Standardeingabe ist normalerweise die Tastatur gemeint. Hierzu ein Beispiel mit der Funktion scanf():

/* scanf1.c */#include <stdio.h>int main (void) { int i; /* ein ganzzahliger Datentyp */ printf("Bitte geben Sie eine Zahl ein : ");  scanf("%d",&i); /* Wartet auf die Eingabe. */ printf("Die Zahl, die Sie eingegeben haben, war %d\n",i); return 0;}

Wenn das Programm korrekt abläuft, wird nach einer Zahl gefragt. Jetzt gibt der Anwender eine Zahl ein und drückt Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (4). Anschließend gibt das Programm die Zahl, die eingegeben wurde, auf dem Bildschirm aus und wird beendet.

Abbildung 4.1 Eine einfache Zahleneingabe mit »scanf()«

Bildlich können Sie sich diesen Vorgang folgendermaßen vorstellen:

Abbildung 4.2 Programm-Ein-/Ausgabe mit »scanf()« und »printf()«

scanf() ist ähnlich aufgebaut wie printf(). Wie bei printf() werden hier zwei Klammern und zwei Hochkommata verwendet. Es wird also formatiert eingelesen. Das Formatzeichen %d steht für die formatierte Eingabe einer dezimalen Zahl. Was aber bedeutet hier das Zeichen »&«?

4.1.1 Der Adressoperator »&«

Den Adressoperator »&« jetzt schon besser zu verstehen, kann nicht schaden. Später, wenn das Thema »Zeiger« (Pointer; siehe Kapitel 12) besprochen wird, kann dieses Vorverständnis von Nutzen sein. Sollte dieser Abschnitt Ihnen ein wenig fremd vorkommen, ist das kein Grund zur Sorge.

Eine Variable kann in die vier folgenden Einzelteile zerlegt werden:

  • Datentyp
  • Name der Variable
  • Speicheradresse der Variable
  • Wert der Variable

Im Programmbeispiel von oben heißt das konkret: Der Datentyp ist int, der Name ist i, und die Adresse wird während der Laufzeit zugewiesen (darauf haben Sie keinen Einfluss). Die Speicheradresse sei hier z. B. 0000:123A. Der Wert ist der, den Sie mit scanf() noch eingeben mussten. Wurde jetzt z. B. 5 eingegeben, ist dieser Speicherplatz wie folgt belegt:

Abbildung 4.3 Eine Variable im Speicher (vereinfacht dargestellt)

Das &-Zeichen ist nichts anderes als der Adressoperator. Dies bedeutet hier, dass der Variablen i vom Typ int mit der Speicheradresse 0000:123A der Wert 5 zugewiesen wird. Oder einfacher: Verschicken Sie eine E-Mail an jemanden, ohne die E-Mail-Adresse anzugeben?


Hinweis

Die Attribute einer Variable wurden hier nur vereinfacht dargestellt. Neben den hier erwähnten Attributen gibt es noch das Zugriffsrecht, den Gültigkeitsbereich und die Lebensdauer einer Variablen. Aber darauf gehe ich erst später in Kapitel 9 ein.

Achtung

Ein Fehler, den Anfänger häufig machen, ist das Weglassen des Adressoperators &.

Was beim Einlesen einer Zeichenkette richtig ist, ist bei anderen Datentypen wie Ganz- oder Gleitpunktzahlen wieder falsch:

/* FALSCH, da Adressoperator & fehlt */scanf("%d", zahl);/* Richtig, denn eine Zeichenkette benötigt keinen Adressoperator.*/scanf("%s", string);

Auch wenn scanf() das Gegenstück zu printf() ist und sich beide in ihrer Schreibweise ähneln, sollten Sie nicht auf die Idee kommen, Folgendes zu schreiben:

/* FALSCH */scanf("Bitte geben Sie eine Zahl ein: %d\n", &zahl);

Das funktioniert deshalb nicht, weil scanf() für die Standardeingabe programmiert ist und printf() für die Standardausgabe. Wobei die Standardausgabe auf der Kommandozeile auch umgeleitet werden kann.

4.1.2 Probleme und deren Behandlung mit »scanf()«

Ein häufiges Problem, das auftritt, wenn Sie scanf() für die Eingabe verwenden, ist die Pufferung. Diese ist je nach System und Anwendung zeilen- oder vollgepuffert. Dies gilt wiederum nicht für die Standardfehlerausgabe (stderr), die laut ANSI C niemals vollgepuffert sein darf. Bevor ich weiter erkläre, sollten Sie folgendes Programm testen:

/* scanf2.c */#include <stdio.h>int main(void) { char a,b,c; printf("1. Buchstabe : "); scanf("%c",&a); printf("2. Buchstabe : "); scanf("%c",&b); printf("3. Buchstabe : "); scanf("%c",&c); printf("Sie gaben ein : %c %c %c ",a,b,c); return 0;}

Folgendes könnte nun vom Programm auf den Bildschirm ausgegeben werden:

Abbildung 4.4 Ein mögliches Problem mit »scanf()« unter Linux

Was ist hier passiert? Warum wird der zweite Buchstabe immer übersprungen? Wie gesagt, das Problem ist hier die Pufferung. Und in C gibt es keinen Befehl (wie etwa chomp bei Perl), um das letzte Zeichen zu entfernen.

In diesem Beispiel wurde als erster Buchstabe »a« eingegeben und Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (15) gedrückt. Dieses Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (16) (ASCII-Code = 10 = \n = newline) befindet sich immer noch im Puffer der Standardeingabe und wird automatisch für das zweite Zeichen verwendet. Was können Sie dagegen tun? Hier gibt es ein paar Möglichkeiten, die allerdings auch systemabhängig sind:

Möglichkeit 1

Sie benutzen die Funktion fflush() zum Entleeren des Tastaturpuffers. Möglicherweise gelingt dies nicht auf jedem Betriebssystem (speziell nicht unter Linux):

/* scanf3.c */#include <stdio.h>int main(void) { char a,b,c; printf("1. Buchstabe : "); scanf("%c",&a);  fflush(stdin); printf("2. Buchstabe : "); scanf("%c",&b);  fflush(stdin); printf("3. Buchstabe : "); scanf("%c",&c); printf("Sie gaben ein : %c %c %c ",a,b,c); return 0;}

Möglichkeit 2

Sie benutzen eine do while-Schleife und ziehen das Newline-Zeichen aus dem Puffer heraus:

/* scanf4.c */#include <stdio.h>int main(void) { char a, b, c; printf("1. Buchstabe : ");  do {scanf("%c",&a);} while ( getchar() != '\n' ); printf("2. Buchstabe : ");  do {scanf("%c",&b);} while ( getchar() != '\n' ); printf("3. Buchstabe : ");  do {scanf("%c",&c);} while ( getchar() != '\n' ); printf("%c %c %c\n", a, b, c); return 0;}

Mehr zur do while-Schleife finden Sie in Abschnitt 8.9.

Möglichkeit 3

Sie verwenden scanf() erst gar nicht (wie dies in der Praxis aus Sicherheitsgründen zu empfehlen ist) und greifen auf eine der vielen anderen Standardeingabe-Funktionen zurück. Ideal wäre es beispielsweise, die Funktion fgets() zum Einlesen zu verwenden und diese Eingabe mit der Funktion sscanf() in ein entsprechendes Format zu konvertieren (siehe auch Abschnitt 16.23). Ein entsprechendes Beispiel könnte so aussehen:

/* scanf5.c */#include <stdio.h>int main(void) { char ch; char buf[2]; printf("Ein Zeichen bitte : "); fgets(buf, 2, stdin); sscanf(buf, "%c", &ch); printf("Das Zeichen : %c\n",ch); return 0;}

Hinweis

In diesem Buch wird noch des Öfteren die Funktion scanf() verwendet. Falls etwas nicht so funktioniert, wie es sollte, beziehen Sie sich auf die drei gezeigten Möglichkeiten in diesem Kapitel.

Achtung

Die Funktion scanf() ist nicht gegen einen Pufferüberlauf (Buffer-Overflow) geschützt und somit unsicher, d. h., sie könnte für einen Hack des Programms durch eine andere Person missbraucht werden. Damit ist gemeint, dass die Funktion nicht die Anzahl der eingegebenen Zeichen überprüft und es damit zu Fehlern kommen kann bzw. ein Fehlverhalten von außen provoziert werden kann. Abgesehen davon ist scanf() (und auch printf()) ein guter Kanidat für Format String Exploits. Viele Compiler monieren scanf() auch als unsichere Funktion. Der Compiler von Microsoft VC++ z. B. rät, stattdessen die Funktion scanf_f()zu verwenden. Beachten Sie hierbei allerdings, das scanf_f() keine Standard-C-Funktion und somit auch nicht portabel ist.

4.1.3 Überprüfen auf das richtige Format

Um sicherzugehen, dass der Benutzer auch das Richtige eingegeben hat, können (müssen) Sie den Rückgabewert von scanf() überprüfen:

/* scanf6.c */#include <stdio.h>int main(void) { char a; int b, check; printf("Bitte Eingabe machen (Zeichen/Zahl): "); check = scanf("%c %d",&a, &b); printf("check = %d \n",check); return 0;}

Der Rückgabewert von scanf() ist dabei immer die Anzahl der erfolgreich gelesenen Werte. Der Wert 0 hingegen wird zurückgegeben, wenn es zu keiner Übereinstimmung mit dem geforderten Formatzeichen gekommen ist. In diesem Beispiel erwartet scanf() die Eingabe eines Zeichens (%c) und einer Dezimalzahl (%d). Wenn beide Eingaben richtig gemacht wurden, sollte die folgende printf()-Anweisung den Wert »2« ausgeben.

Mit einer kleinen Überprüfung können Sie das Programm verbessern, um ein undefiniertes Verhalten für die Weiterarbeit zu verhindern:

/* scanf7.c */#include <stdio.h>int main(void) { int a, b, check; printf("Bitte zwei Zahlen eingeben: "); check = scanf("%d %d",&a ,&b); fflush(stdin); /* unter Linux entfernen */ //getchar(); /* für Linux */ /* Ist check gleich 2, war die Eingabe richtig. */ if(check == 2) printf("Beide Zahlen richtig %d und %d\n",a ,b); /* ... nicht richtig, also war die 2. Zahl falsch. */ else if(check == 1) { printf("Die 2.Zahl hat das falsche Format!!\n"); printf("Bitte Eingabe wiederholen: "); /* noch ein Versuch */ check = scanf("%d",&b); fflush(stdin); if(check) printf("Eingabe Ok. Ihre Zahlen %d %d\n",a,b); else printf("Leider nochmals falsch\n"); } else printf("Die erste oder beide Eingaben waren falsch!\n" ); return 0;}

Bei fehlerfreier Ausführung liefert die scanf()-Funktion die Anzahl der Zeichen zurück, die erfolgreich gelesen, konvertiert und gespeichert wurden.

Wenn die erste Eingabe von scanf() im Beispiel schon fehlerhaft ist, wird die zweite Eingabe gar nicht mehr beachtet. Daher gibt scanf() dann 0 zurück, da gar keine Zeichen gespeichert werden konnten.

Abbildung 4.5 Die Überprüfung des Rückgabewertes von »scanf()«

4.1.4 Zusammenfassung zu »scanf()« Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (22)

Die Funktion scanf() liest zeichenweise eine Folge von Eingabefeldern ein. Für jedes Eingabefeld muss eine Adresse vorhanden sein, wobei das Eingabefeld mit dem Datentyp der Adresse übereinstimmen muss. Bei Erfolg liefert scanf() die Anzahl der erfolgreich eingelesenen Felder zurück. Konnten keine Felder korrekt eingelesen werden, gibt scanf() als Rückgabewert 0 zurück. Für den Fall, dass bei der Eingabe schon ein Fehler auftrat, bevor die Daten überhaupt gelesen werden konnten, wird EOF zurückgegeben.

Folgende Zeichen werden bei scanf() als Eingabefelder akzeptiert:

  • alle Zeichen bis zum nächsten Whitespace
  • alle Zeichen bis zu einer bestimmten Feldbreite von n
  • alle Zeichen bis zu dem ersten Zeichen, das nicht mehr in ein entsprechendes Format konvertiert werden konnte

Whitespace

Ein Whitespace ist ein Leerzeichen, ein Tabulator oder eine Zeilenschaltung.

Anmerkung für den Anfänger

Nochmals eine Anmerkung für die absoluten Neulinge in C: Sie wurden in diesem Kapitel teilweise mit Begriffen wie Variablen, Datentypen, Format, Formatanweisungen, Feldbreite usw. bombardiert, mit denen Sie zum größten Teil wohl noch nichts anfangen können. Den Großteil dieser Begriffe werden Sie aber auf den nächsten Seiten noch genauer kennenlernen. Ich habe die Aufteilung der einzelnen Themen bewusst in dieser Form vorgenommen. Wenn Sie dieses Buch durchgearbeitet haben, werden Sie kein Anfänger mehr sein und hin und wieder das eine oder andere Thema nachschlagen wollen. Dann wird es Ihnen leichter fallen, Informationen zur Funktion scanf() kompakt in ein oder zwei Kapiteln zu finden, anstatt im ganzen Buch verstreut danach suchen zu müssen.

Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

As an enthusiast with substantial expertise in C programming, particularly in input/output operations using functions like printf() and scanf(), I'd like to delve into the concepts presented in the provided article.

The article introduces the scanf() function in C, emphasizing its role in formatted input. Here are the key concepts covered:

  1. scanf() Function Syntax:

    • The scanf() function is introduced with its syntax: int scanf(const char *restrict format, ...);
    • It reads values of different data types in a formatted manner from the standard input (stdin).
  2. Example Usage of scanf():

    • An example program using scanf() is provided, demonstrating the input of an integer from the user and displaying it.
  3. Address Operator (&):

    • The article explains the use of the address operator (&) with scanf().
    • The address operator is introduced by breaking down a variable into its components: data type, variable name, memory address, and value.
  4. Common Mistake: Omission of Address Operator:

    • It warns against the common mistake of omitting the address operator (&) when using scanf() for data types like integers.
  5. Input Buffering Issues with scanf():

    • The article addresses common problems related to input buffering, where newline characters may affect subsequent input.
    • It provides three solutions: using fflush(), a do-while loop, or avoiding scanf() altogether and using alternative input functions like fgets() and sscanf().
  6. Security Issues with scanf():

    • The article highlights security concerns with scanf(), including the risk of buffer overflow and format string exploits.
    • It suggests alternatives like scanf_s() in Microsoft VC++ but notes that it's not portable.
  7. Checking Input Format with scanf():

    • The article suggests checking the return value of scanf() to ensure the correct input format.
    • Examples show how to use the return value to validate input and handle errors.
  8. Summary of scanf() Function:

    • A summary is provided, stating that scanf() reads input character by character, requires addresses for each input field, returns the count of successfully read fields, and returns 0 if no fields are read correctly.
  9. Accepted Input Fields in scanf():

    • Various accepted input fields in scanf() are listed, including characters until whitespace, characters up to a specified width, and characters until the first unconvertible character.
  10. Whitespace Definition:

    • Whitespace is defined as spaces, tabs, or newline characters.

In summary, the article covers the scanf() function in C, addressing its syntax, usage, potential pitfalls, and security considerations. It also introduces related concepts such as the address operator, input buffering, and checking input formats.

Rheinwerk Computing :: C von A bis Z - 4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()« (2024)
Top Articles
Latest Posts
Article information

Author: Dr. Pierre Goyette

Last Updated:

Views: 6445

Rating: 5 / 5 (50 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Dr. Pierre Goyette

Birthday: 1998-01-29

Address: Apt. 611 3357 Yong Plain, West Audra, IL 70053

Phone: +5819954278378

Job: Construction Director

Hobby: Embroidery, Creative writing, Shopping, Driving, Stand-up comedy, Coffee roasting, Scrapbooking

Introduction: My name is Dr. Pierre Goyette, I am a enchanting, powerful, jolly, rich, graceful, colorful, zany person who loves writing and wants to share my knowledge and understanding with you.