Zeichenfolge Eingabe und Ausgabe in C

7

Ich habe diesen Ausschnitt des Codes:

%Vor%

Ich erhalte diese Ausgabe:

%Vor%

meine Eingabe war:

%Vor%

kann jemand erklären warum, und warum dieser Stil der Codierung ist schlecht, danke im Voraus

    
rookie 02.02.2011, 09:51
quelle

3 Antworten

10

scanf reserviert keinen Speicher für Sie.

Sie müssen Speicher für die Variable zuweisen, die an scanf übergeben wird.

Du könntest das so machen:

%Vor%

Aber Warnung:

  1. Die Funktion, die receiveInput aufruft, übernimmt den Besitz des zurückgegebenen Speichers: Sie müssen free(str) angeben, nachdem Sie sie in main gedruckt haben. (Den Besitz auf diese Weise wegzugeben, wird normalerweise nicht als gute Praxis angesehen).

    Eine einfache Lösung ist, den zugewiesenen Speicher als Parameter zu erhalten.

  2. Wenn die Eingabezeichenfolge länger als 99 ist (in meinem Fall), leidet Ihr Programm unter einem Pufferüberlauf (was bereits passiert).

    Eine einfache Lösung besteht darin, an scanf die Länge Ihres Puffers zu übergeben:

    %Vor%

Ein fester Code könnte so aussehen:

%Vor%     
peoro 02.02.2011, 09:53
quelle
20

Mehrere Fragen haben angesprochen, was Sie falsch gemacht haben und wie Sie es beheben können, aber Sie haben auch gesagt (Betonung meiner):

  

kann jemand erklären, warum, und warum dieser Stil der Codierung ist schlecht

Ich denke, scanf ist eine schreckliche Art, Eingaben zu lesen. Es ist nicht konsistent mit printf , macht es leicht zu vergessen, auf Fehler zu prüfen, macht es schwierig, Fehler zu beheben, und ist nicht kompatibel mit gewöhnlichen (und leichter zu tun) Leseoperationen (wie fgets und Firma).

Beachten Sie, dass das "%s" -Format nur lesen wird, bis es Leerzeichen sieht. Warum Whitespace? Warum druckt "%s" eine ganze Zeichenkette, liest aber Zeichenketten in einer so begrenzten Kapazität?

Wenn Sie eine ganze Zeile einlesen möchten, wie Sie es oft tun werden, bietet scanf ... mit "%[^\n]" . Was? Was ist das? Wann wurde daraus Perl?

Aber das wirkliche Problem ist, dass keine davon sicher ist. Sie beide frei überlaufen ohne Grenzen zu überprüfen. Willst du Grenzen überprüfen? Okay, du hast es verstanden: "%10s" (und "%10[^\n]" sieht immer noch schlimmer aus). Dies liest nur 9 Zeichen und fügt automatisch ein abschließendes Null-Zeichen hinzu. Das ist gut ... wenn unsere Array-Größe niemals geändert werden muss .

Was, wenn wir die Größe unseres Arrays als Argument an scanf übergeben wollen? printf kann dies tun:

%Vor%

Möchten Sie dasselbe mit scanf machen? Hier ist wie:

%Vor%

Das stimmt - scanf unterstützt nicht die "%.*s" variable Genauigkeit printf . Um also dynamische Grenzen mit scanf zu überprüfen, müssen wir unsere eigene Formatzeichenfolge erstellen ein temporärer Puffer Das ist alles schlecht, und auch wenn es hier wirklich sicher ist, wird es für jemanden, der einfach reinkommt, eine wirklich schlechte Idee sein.

Schauen wir uns in der Zwischenzeit eine andere Welt an. Schauen wir uns die Welt von fgets an. So lesen wir in einer Datenzeile mit fgets :

%Vor%

Unendlich weniger Kopfschmerzen, keine verschwendete Prozessorzeit, die eine ganzzahlige Genauigkeit in eine Zeichenkette umwandelt, die nur von der Bibliothek wieder in eine ganze Zahl umgewandelt wird, und alle relevanten Elemente sitzen dort auf einer Zeile uns zu sehen, wie sie zusammenarbeiten.

Zugegeben, das liest möglicherweise nicht eine ganze Zeile. Es wird nur eine ganze Zeile gelesen, wenn die Zeile kürzer als bufsize - 1 Zeichen ist. So können wir eine ganze Zeile lesen:

%Vor%

Die Variable curr ist eine Optimierung, die uns daran hindert, Daten, die wir bereits gelesen haben, erneut zu prüfen, und ist unnötig (obwohl nützlich, da wir mehr Daten lesen). Wir könnten sogar den Rückgabewert von strchr verwenden, um das Endezeichen "\n" abzuziehen, wenn Sie dies bevorzugen.

Beachten Sie auch, dass size_t size = 80; als Startplatz völlig willkürlich ist. Wir könnten 81 oder 79 oder 100 verwenden oder es als ein benutzerdefiniertes Argument zu der Funktion hinzufügen. Wir könnten sogar ein Argument int (*inc)(int) hinzufügen und size *= 2; in size = inc(size); ändern, so dass der Benutzer steuern kann, wie schnell das Array wächst. Diese können für die Effizienz von Nutzen sein, wenn Neuzuweisungen kostspielig werden und viele Datenzeilen gelesen und verarbeitet werden müssen.

Wir könnten dasselbe mit scanf schreiben, aber denken Sie daran, wie oft wir die Formatzeichenfolge neu schreiben müssen. Wir könnten es auf ein konstantes Inkrement beschränken, anstatt auf die oben beschriebene Verdoppelung (einfach), und müssen niemals die Formatzeichenfolge anpassen; Wir könnten nachgeben und die Zahl speichern, wie oben beschrieben, und snprintf verwenden, um sie bei jeder Neuzuordnung in zu konvertieren , damit scanf sie zurück konvertieren kann die gleiche Nummer; wir könnten unser Wachstum und unsere Ausgangsposition so begrenzen, dass wir die Formatzeichenfolge manuell anpassen können (zB die Ziffern erhöhen), aber das könnte nach einiger Zeit haarig werden und eine Rekursion (!) erforderlich machen, um sauber zu arbeiten. p>

Außerdem ist es schwierig, das Lesen mit scanf mit dem Lesen mit anderen Funktionen zu mischen. Warum? Angenommen, Sie möchten eine Ganzzahl aus einer Zeile lesen und dann eine Zeichenfolge aus der nächsten Zeile lesen. Sie versuchen dies:

%Vor%

Das wird die "2" lesen, aber fgets liest eine leere Zeile, weil scanf die Zeilenschaltung nicht gelesen hat! Okay, nimm zwei:

%Vor%

Sie denken, dass dies die Zeilenumbrüche auffrischt, und das tut es auch - aber es verschlingt auch führende Leerzeichen auf der nächsten Zeile, weil scanf den Unterschied zwischen Zeilenumbrüchen und anderen Formen von Leerzeichen nicht erkennen kann. (Es stellt sich auch heraus, dass Sie einen Python-Parser schreiben und führende Leerzeichen in Zeilen wichtig sind.) Um dies zu erreichen, müssen Sie getchar oder etwas aufrufen, um den Zeilenumbruch einzulesen und wegzuwerfen:

%Vor%

Ist das nicht albern? Was passiert, wenn Sie scanf in einer Funktion verwenden, aber getchar nicht aufrufen, weil Sie nicht wissen, ob der nächste Lesevorgang scanf oder etwas besser ist (oder ob das nächste Zeichen gerade ist) wird eine Newline sein)?Plötzlich scheint es am besten zu sein, sich für die eine oder andere Situation zu entscheiden: Verwenden wir scanf exklusiv und haben niemals Zugriff auf fgets -style Vollzugriff, oder verwenden wir fgets exklusiv und make es schwieriger, komplexe Parsing durchzuführen?

Eigentlich lautet die Antwort wir nicht . Wir verwenden ausschließlich fgets (oder nicht scanf -Funktionen), und wenn wir scanf -ähnliche Funktionalität benötigen, nennen wir einfach sscanf für die Strings! Das müssen wir nicht tun Haben scanf unnötigerweise unsere Filestreams mucked! Wir können die genaue Kontrolle über unsere Eingabe haben, die wir wollen und immer noch alle Funktionen von scanf Formatierung bekommen. Und selbst wenn wir es nicht könnten, haben viele scanf -Formatoptionen nahezu direkte Funktionen in der Standardbibliothek, wie die unendlich flexibleren Funktionen strtol und strtod (und Freunde). Außerdem ist i = strtoumax(str, NULL) für C99 große Integer-Typen viel sauberer als scanf("%" SCNuMAX, &i); und viel sicherer (wir können diese strtoumax -Zeile für kleinere Typen unverändert verwenden und die implizite Konvertierung die zusätzlichen Bits behandeln, aber mit scanf müssen wir ein temporäres uintmax_t zum Einlesen machen).

Die Moral dieser Geschichte: vermeiden Sie scanf . Wenn Sie die von Ihnen bereitgestellte Formatierung benötigen und dies nicht (effizienter) selbst tun möchten, verwenden Sie fgets / sscanf .

    
Chris Lutz 02.02.2011 11:55
quelle
2

Sie müssen zuerst Ihrem s-Objekt Speicher in Ihrer receiveInput () -Methode zuweisen. Wie:

%Vor%     
Joze 02.02.2011 09:58
quelle

Tags und Links