vamos
Yo uso este código:
while ( scanf("%s", buf) == 1 ){
¿Cuál sería la mejor manera de evitar un posible desbordamiento del búfer para que pueda pasar cadenas de longitudes aleatorias?
Sé que puedo limitar la cadena de entrada llamando, por ejemplo:
while ( scanf("%20s", buf) == 1 ){
Pero preferiría poder procesar cualquier entrada del usuario. ¿O no se puede hacer esto de manera segura usando scanf y debería usar fgets?
jonathan leffler
En su libro La práctica de la programación (que vale la pena leer), Kernighan y Pike discuten este problema y lo resuelven usando snprintf()
para crear la cadena con el tamaño de búfer correcto para pasar a la scanf()
familia de funciones. En efecto:
int scanner(const char *data, char *buffer, size_t buflen)
{
char format[32];
if (buflen == 0)
return 0;
snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
return sscanf(data, format, buffer);
}
Tenga en cuenta que esto todavía limita la entrada al tamaño proporcionado como ‘búfer’. Si necesita más espacio, debe realizar la asignación de memoria o utilizar una función de biblioteca no estándar que realice la asignación de memoria por usted.
Tenga en cuenta que la versión POSIX 2008 (2013) del scanf()
familia de funciones admite un modificador de formato m
(un carácter de asignación-asignación) para entradas de cadena (%s
, %c
, %[
). Instead of taking a char *
argument, it takes a char **
argument, and it allocates the necessary space for the value it reads:
char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
printf("String is: <<%s>>\n", buffer);
free(buffer);
}
If the sscanf()
function fails to satisfy all the conversion specifications, then all the memory it allocated for %ms
-like conversions is freed before the function returns.
-
@Sam: Yes, it should be
buflen-1
— Thank You. You then have to worry about unsigned underflow (wrapping to a rather large number), hence theif
test. I’d be sorely tempted to replace that with anassert()
, or back it up with anassert()
before theif
that fires during development if anyone is careless enough to pass 0 as the size. I’ve not carefully reviewed the documentation for what%0s
means tosscanf()
— the test might be better asif (buflen < 2)
.– Jonathan LefflerSep 11, 2013 at 16:35
-
So
snprintf
writes some data to a string buffer, andsscanf
reads from that created string. Where exactly does this replacescanf
in that it reads from stdin?– krb686Apr 26, 2015 at 14:18
-
It’s also quite confusing that you use the word “format” for your result string and thus pass in “format” as the first argument to
snprintf
yet it is not the actual format parameter.– krb686Apr 26, 2015 at 14:24
-
@krb686: This code is written so that the data to be scanned is in the parameter
data
and hencesscanf()
is appropriate. If you want to read from standard input instead, drop thedata
parameter and callscanf()
instead. As to the choice of the nameformat
for the variable that becomes the format string in the call tosscanf()
, you are entitled to rename it if you wish, but its name is not inaccurate. I am not sure what alternative makes sense; wouldin_format
make it any clearer? I am not planning to change it in this code; you may if you use this idea in your own code.– Jonathan LefflerApr 26, 2015 at 15:28
-
@mabraham: It is still true under macOS Sierra 10.12.5 (up to 2017-06-06) — the
scanf()
on macOS is not documented as supporting%ms
, useful though it would be.– Jonathan LefflerJun 6, 2017 at 19:36
John Ledbetter
If you are using gcc, you can use the GNU-extension a
specifier to have scanf() allocate memory for you to hold the input:
int main()
{
char *str = NULL;
scanf ("%as", &str);
if (str) {
printf("\"%s\"\n", str);
free(str);
}
return 0;
}
Edit: As Jonathan pointed out, you should consult the scanf
man pages as the specifier might be different (%m
) and you might need to enable certain defines when compiling.
-
That’s more an issue of using glibc (the GNU C Library) than of using the GNU C Compiler.
– Jonathan LefflerOct 25, 2009 at 20:24
-
And note that the POSIX 2008 standard provides the
m
modifier to do the same job. Seescanf()
. You’ll need to check whether the systems you use do support this modifier.– Jonathan LefflerFeb 22, 2014 at 21:15
-
GNU (as found on Ubuntu 13.10, at any rate) supports
%ms
. The notation%a
is a synonym for%f
(on output, it requests hexadecimal floating point data). The GNU man page forscanf()
says: _ It is not available if the program is compiled withgcc -std=c99
or gcc -D_ISOC99_SOURCE (unless_GNU_SOURCE
is also specified), in which case thea
is interpreted as a specifier for floating-point numbers (see above)._– Jonathan LefflerFeb 22, 2014 at 21:16
Most of the time a combination of fgets
and sscanf
does the job. The other thing would be to write your own parser, if the input is well formatted. Also note your second example needs a bit of modification to be used safely:
#define LENGTH 42
#define str(x) # x
#define xstr(x) str(x)
/* ... */
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", matriz);
Lo anterior descarta el flujo de entrada hasta, pero sin incluir, la nueva línea (\n
) personaje. Deberá agregar un getchar()
para consumir esto. También verifique si llegó al final de la transmisión:
if (!feof(stdin)) { ...
y eso es todo.
-
¿Podrías poner el
feof
código en un contexto más grande? Lo pregunto ya que esa función a menudo se usa mal.– Roland Illig
24/10/2016 a las 21:52
-
array
necesita serchar array[LENGTH+1];
– jxh
19 de marzo de 2019 a las 22:58
-
Voto negativo por presentar el infame
!feof
patrón sin ningún contexto ni explicación y sin arreglarlo durante 5 años.– Roland Illig
23 de diciembre de 2021 a las 11:17
usando directamente scanf(3)
y sus variantes plantea una serie de problemas. Por lo general, los usuarios y los casos de uso no interactivos se definen en términos de líneas de entrada. Es raro ver un caso en el que, si no se encuentran suficientes objetos, más líneas resolverán el problema, pero ese es el modo predeterminado para scanf. (Si un usuario no sabía cómo ingresar un número en la primera línea, una segunda y una tercera línea probablemente no ayuden).
al menos si tu fgets(3)
sabe cuántas líneas de entrada necesitará su programa, y no tendrá ningún desbordamiento de búfer…
Limitar la longitud de la entrada es definitivamente más fácil. Podría aceptar una entrada arbitrariamente larga usando un bucle, leyendo un bit a la vez, reasignando espacio para la cadena según sea necesario…
Pero eso es mucho trabajo, por lo que la mayoría de los programadores de C simplemente cortan la entrada en una longitud arbitraria. Supongo que ya lo sabe, pero usar fgets() no le permitirá aceptar cantidades arbitrarias de texto; aún necesitará establecer un límite.
-
Entonces, ¿alguien sabe cómo hacer eso con scanf entonces?
– vaya
25 de octubre de 2009 a las 17:17
-
El uso de fgets en un bucle puede permitirle aceptar cantidades arbitrarias de texto, solo mantenga
realloc()
ing su búfer.– bdonlan
25 de octubre de 2009 a las 18:53
No es mucho trabajo hacer una función que asigne la memoria necesaria para su cadena. Esa es una pequeña función c que escribí hace algún tiempo, siempre la uso para leer cadenas.
Devolverá la cadena de lectura o si se produce un error de memoria NULL. Pero tenga en cuenta que debe liberar () su cadena y siempre verificar su valor de retorno.
#define BUFFER 32
char *readString()
{
char *str = malloc(sizeof(char) * BUFFER), *err;
int pos;
for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
{
if(pos % BUFFER == BUFFER - 1)
{
if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
free(str);
str = err;
}
}
if(str != NULL)
str[pos] = '\0';
return str;
}
-
Entonces, ¿alguien sabe cómo hacer eso con scanf entonces?
– vaya
25 de octubre de 2009 a las 17:17
-
El uso de fgets en un bucle puede permitirle aceptar cantidades arbitrarias de texto, solo mantenga
realloc()
ing su búfer.– bdonlan
25 de octubre de 2009 a las 18:53