Ich möchte die Nachteile wissen scanf()
.
Auf vielen Seiten habe ich das mit gelesen scanf
kann zu Pufferüberläufen führen. Was ist der Grund dafür? Gibt es noch andere Nachteile mit scanf
?
karthi_ms
Ich möchte die Nachteile wissen scanf()
.
Auf vielen Seiten habe ich das mit gelesen scanf
kann zu Pufferüberläufen führen. Was ist der Grund dafür? Gibt es noch andere Nachteile mit scanf
?
AnT steht zu Russland
Die meisten Antworten scheinen sich bisher auf das Problem des String-Pufferüberlaufs zu konzentrieren. In Wirklichkeit die Formatbezeichner, die verwendet werden können scanf
Funktionen unterstützen explizit Feldbreite -Einstellung, die die maximale Größe der Eingabe begrenzt und einen Pufferüberlauf verhindert. Dies macht die populären Anschuldigungen des String-Pufferüberlaufs zu Gefahren in scanf
praktisch grundlos. Behaupten, dass scanf
ist irgendwie analog zu gets
in der Hinsicht ist völlig falsch. Es gibt einen großen qualitativen Unterschied zwischen scanf
und gets
: scanf
stellt dem Benutzer Funktionen zur Verhinderung von String-Pufferüberläufen zur Verfügung, während gets
nicht.
Man kann argumentieren, dass diese scanf
Funktionen sind schwierig zu verwenden, da die Feldbreite in die Formatzeichenfolge eingebettet werden muss (es gibt keine Möglichkeit, sie durch ein variadisches Argument zu übergeben, wie dies in möglich ist printf
). Das stimmt tatsächlich. scanf
ist in dieser Hinsicht in der Tat ziemlich schlecht konzipiert. Aber das behauptet doch jeder scanf
bezüglich der String-Buffer-Overflow-Sicherheit irgendwie hoffnungslos kaputt ist, sind völlig falsch und werden normalerweise von faulen Programmierern gemacht.
Das eigentliche Problem mit scanf
hat eine ganz andere Natur, obwohl es auch darum geht Überlauf. Wann scanf
-Funktion zum Konvertieren von Dezimaldarstellungen von Zahlen in Werte arithmetischer Typen verwendet wird, bietet sie keinen Schutz vor arithmetischem Überlauf. Wenn es zu einem Überlauf kommt, scanf
erzeugt undefiniertes Verhalten. Aus diesem Grund ist die einzig richtige Möglichkeit, die Konvertierung in der C-Standardbibliothek durchzuführen, Funktionen von strto...
Familie.
Also, um das oben Gesagte zusammenzufassen, das Problem mit scanf
ist, dass es schwierig (wenn auch möglich) ist, String-Puffer richtig und sicher zu verwenden. Und es ist unmöglich, es sicher für arithmetische Eingaben zu verwenden. Letzteres ist das eigentliche Problem. Ersteres ist nur eine Unannehmlichkeit.
PS Das Obige soll sich auf die gesamte Familie beziehen scanf
Funktionen (einschließlich auch fscanf
und sscanf
). Mit scanf
Das offensichtliche Problem besteht insbesondere darin, dass die Idee, eine streng formatierte Funktion zum Lesen zu verwenden, potenziell besteht interaktiv Eingabe ist eher fragwürdig.
Ich muss nur darauf hinweisen, dass Sie arithmetische Eingaben nicht sicher lesen können, sondern dass Sie sie nicht korrekt ausführen können und robust für Dirty-Input. Für mich gibt es einen großen Unterschied zwischen dem Absturz meines Programms und/oder dem Öffnen des Betriebssystems für einen Angriff und dem einfachen Erhalten einiger falscher Werte, wenn Benutzer absichtlich Unheil anstellen. Was kümmert es mich, wenn sie 1431337.4044194872987 eingeben und stattdessen 4,0 erhalten? So oder so haben sie 4.0 eingegeben. (Manchmal mag es wichtig sein, aber wie oft?)
– Benutzer645280
3. Januar 2014 um 14:48 Uhr
Dritter Absatz: scanf liest gerne einen Wert von> 2 ^ 32, wenn er in der Zeichenfolge gefunden wird, in eine 32-Bit-Ganzzahl und verursacht undefiniertes Verhalten?
– 2501
20. Januar 2016 um 14:52 Uhr
@2501: Ja, genau. Zumindest passiert das laut Sprachstandard.
– AnT steht zu Russland
20. Januar 2016 um 15:38 Uhr
“Behaupten, dass scanf
ist irgendwie analog zu bekommt in der Hinsicht ist völlig falsch.” Ich verstehe es, scanf
wenigstens tut Sie können die maximale Feldgröße angeben, aber die ideologische Verwendung von %s
hat sicherlich die gleichen Probleme wie gets
, und wie bei vielen anderen gefährlichen, aber nützlichen Werkzeugen in C sind sie alle leicht zu missbrauchen. Eben strtoul
hat seine Gefahren, also anstatt vorzuschlagen, dass die Leute aufhören, es zu benutzen Teile von C, können wir nicht einfach dazu übergehen, den Leuten vorzuschlagen, dass sie aufhören sollten alles C?
– autistisch
11. Juli 2018 um 23:49 Uhr
paxdiablo
Die Probleme mit scanf sind (mindestens):
%s
um eine Zeichenfolge vom Benutzer zu erhalten, was zu der Möglichkeit führt, dass die Zeichenfolge länger als Ihr Puffer ist und einen Überlauf verursacht.Ich benutze sehr viel lieber fgets
ganze Zeilen einzulesen, um die gelesene Datenmenge zu begrenzen. Wenn Sie einen 1K-Puffer haben und eine Zeile mit hineinlesen fgets
Sie können erkennen, ob die Zeile zu lang war, weil das Zeilenumbruchzeichen fehlt (trotz der letzten Zeile einer Datei ohne Zeilenumbruch).
Dann können Sie sich beim Benutzer beschweren oder dem Rest der Zeile mehr Platz zuweisen (kontinuierlich, wenn nötig, bis Sie genug Platz haben). In beiden Fällen besteht kein Risiko eines Pufferüberlaufs.
Sobald Sie die Zeile gelesen haben, können Sie kennt dass Sie in der nächsten Zeile positioniert sind, sodass es dort kein Problem gibt. Sie können dann sscanf
Ihre Zeichenfolge nach Herzenslust, ohne den Dateizeiger zum erneuten Lesen speichern und wiederherstellen zu müssen.
Hier ist ein Codeausschnitt, den ich häufig verwende, um sicherzustellen, dass kein Pufferüberlauf auftritt, wenn ich den Benutzer nach Informationen frage.
Es könnte leicht angepasst werden, um bei Bedarf eine andere Datei als die Standardeingabe zu verwenden, und Sie könnten es auch einen eigenen Puffer zuweisen lassen (und ihn weiter erhöhen, bis er groß genug ist), bevor Sie diesen an den Aufrufer zurückgeben (obwohl der Aufrufer dann verantwortlich wäre). für die Befreiung natürlich).
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Size zero or one cannot store enough, so don't even
// try - we need space for at least newline and terminator.
if (sz < 2)
return SMALL_BUFF;
// Output prompt.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
// Get line with buffer overrun protection.
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// Catch possibility of `\0` in the input stream.
size_t len = strlen(buff);
if (len < 1)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[len - 1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[len - 1] = '\0';
return OK;
}
Und ein Testtreiber dafür:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
Abschließend ein Testlauf, um es in Aktion zu zeigen:
$ printf "\0" | ./tstprg # Singular NUL in input stream.
Enter string>
No input
$ ./tstprg < /dev/null # EOF in input stream.
Enter string>
No input
$ ./tstprg # A one-character string.
Enter string> a
OK [a]
$ ./tstprg # Longer string but still able to fit.
Enter string> hello
OK [hello]
$ ./tstprg # Too long for buffer.
Enter string> hello there
Input too long [hello the]
$ ./tstprg # Test limit of buffer.
Enter string> 123456789
OK [123456789]
$ ./tstprg # Test just over limit.
Enter string> 1234567890
Input too long [123456789]
if (fgets (buff, sz, stdin) == NULL) return NO_INPUT;
Warum hast du verwendet NO_INPUT
als Rückgabewert? fgets
kehrt zurück NULL
nur auf Fehler.
– Fabio Carello
29. März 2013 um 17:19 Uhr
@Fabio, nicht ganz. Es gibt auch null zurück, wenn der Stream geschlossen wird, bevor eine Eingabe erfolgt ist. Das ist der Fall, der hier gefangen wird. Machen Sie nicht den Fehler, dass NO_INPUT eine leere Eingabe bedeutet (drücken Sie ENTER vor allem anderen) – Letzteres gibt Ihnen eine leere Zeichenfolge ohne NO_INPUT-Fehlercode.
– paxdiablo
29. März 2013 um 23:21 Uhr
Der neueste POSIX-Standard erlaubt char *buf; scanf("%ms", &buf);
die Ihnen genügend Platz zuweist malloc
(daher muss es später freigegeben werden), was dazu beitragen würde, Pufferüberläufe zu verhindern.
– Traumlax
3. Oktober 2014 um 1:14 Uhr
Was passiert, wenn wir anrufen getLine
mit 1
als die sz
Parameter? if (buff[strlen(buff)-1] != '\n')
da tritt das problem auf. Vielleicht if (!sz) { return TOO_LONG; } if (buff[sz = strcspn(buff, "\n")] == '\n' || getchar() == '\n') { buff[sz] = '\0'; return OK; } unsigned char c; while (fread(&c, 1, 1, stdin) == 1 && c != '\n'); return TOO_LONG;
was in der Tat Gewohnheit überlaufen, wenn Sie passieren sz <= 1
und hat den zusätzlichen Vorteil, das zu entfernen '\n'
für Sie ohne Overhead, obwohl es bemerkt werden sollte, dass Ihr Code könnte durch strategischen Einsatz verbessert werden scanf
…
– autistisch
11. Juli 2018 um 23:21 Uhr
Das ist ein guter Fang, @chux, ich habe eine zusätzliche Prüfung dafür hinzugefügt, um es als “keine Eingabe” zu behandeln. Der Test wurde mit durchgeführt printf "\0" | exeName
um das ursprüngliche Problem zu überprüfen und zu beheben. Ich schätze, ich habe noch nie mit einem so verrückten Eingabeszenario nachgesehen (aber ich verdammt gut sollte haben). Danke für die Warnung.
– paxdiablo
1. September 2020 um 2:00 Uhr
jamesdlin
Aus der comp.lang.c-FAQ: Warum sagt jeder, dass man scanf nicht verwenden soll? Was sollte ich stattdessen verwenden?
scanf
hat eine Reihe von Problemen – siehe Fragen 12.17, 12.18aund 12.19. Auch seine%s
Format hat das gleiche Problem, dassgets()
hat (siehe Frage 23.12) – es ist schwer zu garantieren, dass der Empfangspuffer nicht überläuft. [footnote]Allgemeiner,
scanf
ist für eine relativ strukturierte, formatierte Eingabe konzipiert (der Name leitet sich tatsächlich von „scan formatted“ ab). Wenn Sie darauf achten, wird es Ihnen sagen, ob es erfolgreich war oder fehlgeschlagen ist, aber es kann Ihnen nur ungefähr sagen, wo es fehlgeschlagen ist, und überhaupt nicht, wie oder warum. Sie haben nur sehr wenige Möglichkeiten, Fehler zu beheben.Dennoch ist die interaktive Benutzereingabe die am wenigsten strukturierte Eingabe, die es gibt. Eine gut gestaltete Benutzeroberfläche ermöglicht es dem Benutzer, fast alles einzugeben – nicht nur Buchstaben oder Satzzeichen, wenn Ziffern erwartet wurden, sondern auch mehr oder weniger Zeichen als erwartet oder überhaupt keine Zeichen (dh, nur die RETURN-Taste), oder vorzeitiges EOF oder irgendetwas. Es ist fast unmöglich, mit all diesen potenziellen Problemen bei der Verwendung elegant umzugehen
scanf
; Es ist viel einfacher, ganze Zeilen zu lesen (mitfgets
oder dergleichen), interpretieren Sie sie dann entweder mitsscanf
oder einige andere Techniken. (Funktionen wiestrtol
,strtok
undatoi
sind oft nützlich; siehe auch Fragen 12.16 und 13.6.) Wenn Sie welche verwendenscanf
Variante überprüfen Sie unbedingt den Rückgabewert, um sicherzustellen, dass die erwartete Anzahl von Elementen gefunden wurde. Auch wenn Sie verwenden%s
stellen Sie sicher, dass Sie sich vor einem Pufferüberlauf schützen.Beachten Sie übrigens, dass Kritik an
scanf
sind nicht unbedingt Anklagen gegenfscanf
undsscanf
.scanf
liest ausstdin
, die normalerweise eine interaktive Tastatur ist und daher am wenigsten eingeschränkt ist und zu den meisten Problemen führt. Wenn eine Datendatei andererseits ein bekanntes Format hat, kann es angebracht sein, sie damit zu lesenfscanf
. Es ist perfekt geeignet, um Zeichenfolgen mit zu analysierensscanf
(solange der Rückgabewert geprüft wird), weil es so einfach ist, die Kontrolle wiederzuerlangen, den Scan neu zu starten, die Eingabe zu verwerfen, wenn sie nicht übereinstimmt, usw.Weiterführende Links:
Referenzen: K&R2 Sek. 7,4 p. 159
Es ist sehr schwer zu bekommen scanf
um das zu tun, was du willst. Sicher, das kannst du, aber Dinge wie scanf("%s", buf);
sind genauso gefährlich wie gets(buf);
wie alle gesagt haben.
Als Beispiel kann das, was Paxdiablo in seiner Funktion zum Lesen tut, mit etwas wie:
scanf("%10[^\n]%*[^\n]", buf));
getchar();
Das Obige liest eine Zeile und speichert die ersten 10 Nicht-Zeilenumbruchzeichen darin buf
, und verwerfen Sie dann alles bis (einschließlich) einem Zeilenumbruch. Die Funktion von paxdiablo könnte also mit geschrieben werden scanf
folgender Weg:
#include <stdio.h>
enum read_status {
OK,
NO_INPUT,
TOO_LONG
};
static int get_line(const char *prompt, char *buf, size_t sz)
{
char fmt[40];
int i;
int nscanned;
printf("%s", prompt);
fflush(stdout);
sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
/* read at most sz-1 characters on, discarding the rest */
i = scanf(fmt, buf, &nscanned);
if (i > 0) {
getchar();
if (nscanned >= sz) {
return TOO_LONG;
} else {
return OK;
}
} else {
return NO_INPUT;
}
}
int main(void)
{
char buf[10+1];
int rc;
while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
if (rc == TOO_LONG) {
printf("Input too long: ");
}
printf("->%s<-\n", buf);
}
return 0;
}
Eines der anderen Probleme mit scanf
ist sein Verhalten bei Überlauf. Zum Beispiel beim Lesen einer int
:
int i;
scanf("%d", &i);
Die oben genannten können im Falle eines Überlaufs nicht sicher verwendet werden. Selbst im ersten Fall ist das Lesen eines Strings viel einfacher zu handhaben fgets
eher als mit scanf
.
Ja, du hast recht. Da ist eine große Sicherheitslücke drin scanf
Familie(scanf
,sscanf
, fscanf
..etc), insbesondere beim Lesen eines Strings, da sie die Länge des Puffers (in den sie lesen) nicht berücksichtigen.
Beispiel:
char buf[3];
sscanf("abcdef","%s",buf);
eindeutig der Puffer buf
kann MAX halten 3
verkohlen. Aber die sscanf
werde versuchen zu setzen "abcdef"
hinein, was zu einem Pufferüberlauf führt.
Sie können “%10s” als Formatbezeichner angeben und es werden nicht mehr als 10 Zeichen in den Puffer eingelesen.
– Traumlax
12. März 2010 um 3:29 Uhr
Sicher – es ist möglich, die API sicher zu verwenden. Es ist auch möglich, Dynamit zu verwenden, um Schmutz sicher aus Ihrem Garten zu entfernen. Aber ich würde es auch nicht empfehlen, zumal es sicherere Alternativen gibt.
– Larry Ostermann
12. März 2010 um 3:34 Uhr
Mein Vater benutzte früher Gelignite, um Bäume auf der Farm zu fällen. Sie müssen nur Ihre Werkzeuge verstehen und die Gefahren kennen.
– paxdiablo
12. März 2010 um 3:42 Uhr
@codaddict: Die Tatsache, dass jemand die Feldbreite nicht mit verwendet scanf
ist das problem mit dem jemand, nicht mit scanf
. Es ist völlig irrelevant für das fragliche Thema. Das ist schließlich C, nicht Java.
– AnT steht zu Russland
12. März 2010 um 7:44 Uhr
Das Problem ist, dass die Feldbreite in scanf()
muss im Konvertierungsbezeichner fest codiert sein; mit printf()
können Sie verwenden *
im Konvertierungsbezeichner und übergeben Sie die Länge als Argument. Aber seit *
bedeutet etwas anderes in scanf()
, das funktioniert nicht, also muss man im Grunde für jeden Lesevorgang ein neues Format generieren, wie es Alok in seinem Beispiel tut. Es fügt nur mehr Arbeit und Unordnung hinzu; könnte man auch verwenden fgets()
und fertig damit.
– Johannes Bode
12. März 2010 um 15:23 Uhr
Johannes Bode
Probleme habe ich mit der *scanf()
Familie:
printf()
, you can’t make it an argument in the scanf()
call; it must be hardcoded in the conversion specifier.scanf("%d", &value);
will successfully convert and assign 12 to value
, leaving the “w4” stuck in the input stream to foul up a future read. Ideally the entire input string should be rejected, but scanf()
doesn’t give you an easy mechanism to do that. If you know your input is always going to be well-formed with fixed-length strings and numerical values that don’t flirt with overflow, then scanf()
is a great tool. If you’re dealing with interactive input or input that isn’t guaranteed to be well-formed, then use something else.
You can provide “%10s” as the format specifier and it will read no more than 10 characters into the buffer.
Mar 12, 2010 at 3:29
Sure – it’s possible to use the API safely. It’s also possible to use dynamite to clear dirt out of your garden safely. But I wouldn’t recommend either, especially since there are safer alternatives.
Mar 12, 2010 at 3:34
My dad used to use gelignite for clearing down trees on the farm. You just have to understand your tools and know the dangers.
Mar 12, 2010 at 3:42
@codaddict: The fact that someone doesn’t use field width with scanf
is the problem with that someone, not with scanf
. It is completely irrelevant to the issue in question. This is C after all, not Java.
Mar 12, 2010 at 7:44
The problem is that the field width in scanf()
must be hardcoded in the conversion specifier; with printf()
, you can use *
in the conversion specifier and pass the length as an argument. But since *
means something different in scanf()
, that doesn’t work, so you basically have to generate a new format for each read like Alok does in his example. It just adds more work and clutter; might as well use fgets()
and be done with it.
Mar 12, 2010 at 15:23
dreamlax
Many answers here discuss the potential overflow issues of using scanf("%s", buf)
, but the latest POSIX specification more-or-less resolves this issue by providing an m
assignment-allocation character that can be used in format specifiers for c
, s
, and [
formats. This will allow scanf
to allocate as much memory as necessary with malloc
(so it must be freed later with free
).
An example of its use:
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.
// use buf
free(buf);
See here. Disadvantages to this approach is that it is a relatively recent addition to the POSIX specification and it is not specified in the C specification at all, so it remains rather unportable for now.
Siehe auch Ein Leitfaden für Anfänger weg von
scanf()
.– Jonathan Leffler
19. Juli 2017 um 4:41 Uhr