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:
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.
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:
Ein fester Code könnte so aussehen:
%Vor%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:
Möchten Sie dasselbe mit scanf
machen? Hier ist wie:
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
:
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:
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 Außerdem ist es schwierig, das Lesen mit Das wird die "2" lesen, aber 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 Ist das nicht albern? Was passiert, wenn Sie Eigentlich lautet die Antwort wir nicht . Wir verwenden ausschließlich Die Moral dieser Geschichte: vermeiden Sie 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>
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: fgets
liest eine leere Zeile, weil scanf
die Zeilenschaltung nicht gelesen hat! Okay, nimm zwei: 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: 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? 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). scanf
. Wenn Sie die von Ihnen bereitgestellte Formatierung benötigen und dies nicht (effizienter) selbst tun möchten, verwenden Sie fgets
/ sscanf
.