Debo usar fgets o scanf con limitadas de entrada en c?

0

Pregunta

Debo usar fgets o el formato scanf como scanf("%10s", foo).

Exceptuados los que scanf no leer caracteres en blanco, que puede ser resuelto y hacer más materias con scanset, entonces ¿por qué debo utilizar fgets en lugar de scanf?

Cualquier ayuda se agradece.


Editar

Una cosa más que quiero preguntar es: incluso cuando la usamos fgets ¿qué sucede si el usuario ingresa los caracteres más de límite (me refiero a un montón de personajes), conduce al desbordamiento de búfer? Entonces, ¿cómo lidiar con ella?

c fgets input scanf
2021-11-23 13:53:00
4

Mejor respuesta

5

En la mayoría de los sistemas operativos, la entrada del usuario, por defecto, basado en la línea. Una razón para esto es para permitir que el usuario pulse la tecla de retroceso para corregir la entrada, antes de enviar la entrada al programa.

Para la línea de entrada de usuario, es significativo e intuitiva para un programa para leer una línea de entrada a la vez. Esto es lo que la función de fgets no (siempre que la memoria es lo suficientemente grande para almacenar la totalidad de la línea de entrada).

La función scanf, por otro lado, normalmente no lee una línea de entrada a la vez. Por ejemplo, cuando se utiliza el %s o %d la conversión de especificador de formato con scanf, no va a consumir toda una línea de entrada. En su lugar, sólo consumir tanto de entrada como los partidos de la conversión especificador de formato. Esto significa que el carácter de nueva línea al final de la línea que normalmente no se consume (que fácilmente puede conducir a errores de programación). También, scanf llama con el %d la conversión de especificador de formato tendrá en cuenta los comentarios tales como 6sldf23dsfh2 como entrada válida para el número de 6pero ninguna más llamadas a scanf con el mismo especificador va a fallar, a menos que descartar el resto de la línea de la secuencia de entrada.

Este comportamiento de scanf es contra-intuitivo, mientras que el comportamiento de fgets es intuitivo, cuando se trata de la línea de base de la entrada del usuario.

Después de usar fgets, usted puede usar la función sscanf en la cadena, para analizar el contenido de una línea individual. Esto le permitirá seguir utilizando scansets. O usted puede analizar la línea por otros medios. De cualquier forma, siempre y cuando esté utilizando fgets en lugar de scanf para la lectura de la entrada, que será el manejo de una línea de entrada en un tiempo, que es la forma natural e intuitiva de acuerdo con la línea de entrada de usuario.

Cuando usamos fgets ¿qué sucede si el usuario ingresa los caracteres más de límite (me refiero a un montón de personajes), conduce al desbordamiento de búfer? Entonces, ¿cómo lidiar con ella?

Si el usuario introduce más caracteres de los que caben en el buffer especificado por el segundo fgets argumento de la función, entonces no va a desbordar el búfer. En su lugar, sólo extraer tantos caracteres del flujo de entrada como caben en el buffer. Usted puede determinar si la totalidad de la línea fue leído por comprobar si la cadena contiene un carácter de nueva línea '\n' en la final.

2021-11-23 15:13:39

Muchas gracias, tu respuesta realmente útil para mí.
Becker

El comportamiento de fgets() sólo es intuitiva para las entradas que no son más de lo esperado.
supercat
2

Este es un comúnmente discutido tema, llena de opinión, pero muy interesante, nada menos. He observado que una gran mayoría de aquellos que ya han respondido a preguntas similares en este sitio caer en el lado de fgets(). Yo soy uno de ellos. Me parece fgets() mucho mejor la de usar para el usuario de entrada de scanf() con pocas excepciones. scanf() es considerado por muchos como sub-método óptimo para el manejo de la entrada del usuario. Por ejemplo

"...se le dirá si tuvo éxito o no, pero se puede decir que sólo aproximadamente, donde se produjo el error, y no en todos, cómo o por qué. Usted tienen muy poca oportunidad de recuperación de errores."
(jamesdlin). Pero en el interés de procurar el equilibrio, va a empezar citando esta discusión.

Para la entrada de usuario que viene de stdin, es decir, la entrada de teclado, fgets() será una mejor opción. Es mucho más permisiva en que la cadena se lee puede ser validados antes de la conversión se intenta

Uno de los pocos momentos en que el uso de un formulario de scanf(): fscanf() estaría bien podría ser cuando la conversión de la entrada de un muy controlados en origen, es decir, a partir de la lectura estrictamente un formato de archivo con la repetición de predicción de los campos.

Para más discusión, esta comparación de los dos aspectos más destacados adicionales ventajas y desventajas de ambos.

Edit: a la dirección de OP pregunta adicional acerca de desbordamiento:

"Una cosa más que quiero preguntar es: incluso cuando usar fgets ¿qué sucede si el usuario ingresa los caracteres más de límite (me refiero a un montón de los personajes), conduce al desbordamiento de búfer? Entonces, ¿cómo lidiar con es así?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm está muy bien diseñado para evitar desbordamiento de búfer, simplemente mediante el uso de sus parámetros correctamente, por ejemplo:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Esto impide la entrada mayor que el tamaño de búfer de ser procesados, evitando el desbordamiento.

incluso el uso de scanf()la prevención de desbordamiento de búfer es bastante sencillo: Usar un especificador de ancho en la cadena de formato. Si quieres leer la entrada, por ejemplo, y el límite de tamaño de entrada de un usuario a 100 caracteres como máximo, el código se incluyen las siguientes:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

Sin embargo, con los números, desbordamiento no es tan agradable de usar scanf(). Para demostrar el uso de este simple código, la introducción de los dos valores indicados en el comentario de uno por ejecutar:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

Para el segundo valor, el sistema arroja el siguiente:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Aquí usted necesita para leer una cadena de texto, a continuación, siga con una cadena a número de la conversión de uso de uno de los strto_() funciones: strtol(), strtod(), ...). Incluyen la capacidad de la prueba para el desbordamiento antes de causar un tiempo de ejecución de advertencia o de error. Tenga en cuenta que el uso de atoi(), atod() no va a proteger de desbordamiento de cualquiera.

2021-11-23 14:10:20

Gracias, realmente aprecio su respuesta.
Becker

Una pregunta, la "llena de opinión"? Debo respetuosamente en desacuerdo. No es una cuestión de opinión que scanf es casi completamente inútil, bueno mejor para la lectura individual, las entradas simples en introducción a los programas en C, pero prohibitivamente difícil hacer algo remotamente sofisticado con — estos son hechos evidentes! :-)
Steve Summit
1

Hasta ahora, todas las respuestas que aquí se presenta complejidades de scanf y fgetspero lo que yo creo que vale la pena mencionar, es que ambas funciones se utilizan en la corriente de C estándar. Scanf es especialmente peligroso, porque tiene todo tipo de problemas de seguridad con los desbordamientos de búfer. fgets no es tan problemático, pero desde mi experiencia, que tiende a ser un poco torpe y no tan útiles en la práctica.

La verdad es que, a menudo, no se sabe realmente cuánto tiempo la entrada de usuario será. Usted puede evitar esto mediante el uso de fgets con espero que este lo suficientemente grande como buffer, pero eso no es realmente ellegant. En su lugar, lo que a menudo quieren es tener la dinámica de búfer, que crecerá para ser lo suficientemente grande para almacenar todo lo que la entrada del usuario va a entregar. Y esto es cuando getline función entra en juego. Se utiliza para leer cualquier número de caracteres desde el usuario, hasta \n se encuentra. Esencialmente, se carga toda la línea a su memoria, como una cadena.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Esta función toma un puntero a una asignación dinámica de cadena como primer argumento, y un puntero a un tamaño del búfer asignado como segundo argumento y corriente como tercer argumento. (se le esencialmente lugar stdin hay para entrada de línea de comandos). Y devuelve el número de caracteres de lectura, incluyendo \n al final, pero no el nulo de terminación.

Aquí, usted puede ver un ejemplo de uso de esta función:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Por supuesto, el uso de esta función requiere de conocimientos básicos acerca de los punteros y memoria dinámica en C, sin embargo un poco más complicada naturaleza de getline es definitivamente vale la pena, porque de la seguridad y la flexibilidad.

Usted puede leer más acerca de esta función, y otras funciones de entrada disponible en C, en este sitio web: https://www.studymite.com/blog/strings-in-c Creo que es un resumen de las complejidades de la C entrada bastante bien.

2021-11-23 19:18:00

Gracias por tu consejo y el enlace, me ayuda mucho.
Becker
1

Si usted tiene por ejemplo una matriz de caracteres declarado como

char s[100];

y quieren leer una cadena de caracteres que contiene espacios integrados, a continuación, puede utilizar cualquiera de los scanf la siguiente manera:

scanf( "%99[^\n]", s );

o fgets como:

fgets( s, sizeof( s ), stdin );

La diferencia entre estos dos llamadas es que la llamada de scanf no leer el carácter de nueva línea '\n' desde el búfer de entrada. Mientras fgets lee el carácter de nueva línea '\n' si no hay suficiente espacio en la matriz de caracteres.

Para eliminar el carácter de nueva línea '\n' que se almacena en la matriz de caracteres después de usar fgets usted puede escribir por ejemplo:

s[ strcspn( s, "\n" ) ] = '\0';

Si la cadena de entrada tiene más de 99 caracteres, a continuación, las dos llamadas de sólo lectura de 99 caracteres y anexar la secuencia de caracteres con la terminación de carácter cero '\0'. Todos los caracteres restantes serán aún en el búfer de entrada.

Hay un problema con fgets. Por ejemplo, si antes de fgets no se utiliza scanf como por ejemplo:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

y la entrada del usuario es:

10
Hello World

luego de la llamada de fgets leer solo el carácter de nueva línea '\n' que se almacena en el buffer después de pulsar la tecla Intro cuando el valor de entero en la convocatoria de scanf fue leído.

En este caso, usted necesita para escribir un código que le quite el carácter de nueva línea '\n' antes de llamar fgets.

Usted puede hacer esto, por ejemplo, de la siguiente manera:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Si usted está usando scanf a continuación, en tal situación, se puede escribir:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

En otros idiomas

Esta página está en otros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Slovenský
..................................................................................................................