Usar fscanf() frente a fgets() y sscanf()

5 minutos de lectura

En el libro Programación práctica en C, encuentro que la combinación de fgets() y sscanf() se utiliza para leer la entrada. Sin embargo, me parece que el mismo objetivo se puede lograr más fácilmente usando solo el fscanf() función:

Del libro (la idea, no el ejemplo):

int main()
{
    int age, weight;
    printf("Enter age and weight: ");

    char line[20];
    fgets(line, sizeof(line), stdin);
    sscanf(line, "%d %d", &age, &weight);

    printf("\nYou entered: %d %d\n", age, weight);
    return 0;
}

Como creo que debería ser:

int main()
{
    int age, weight;
    printf("Enter age and weight: ");
    fscanf(stdin, "%d %d", &age, &weight);

    printf("\nYou entered: %d %d\n", age, weight);
    return 0;
}

¿O hay alguna peculiaridad oculta que me estoy perdiendo?

  • ¿Puedes publicar el fragmento de código del libro para que podamos ver las diferencias?

    – Ingeniero2021

    11/03/2014 a las 16:39

  • Hecho. Por favor mira. Ambos programas producen resultados idénticos (al menos a simple vista).

    – ankush981

    11 de marzo de 2014 a las 16:42


Hay algunas diferencias de comportamiento en los dos enfoques. Si utiliza fgets() + sscanf()debe introducir ambos valores en la misma línea, mientras que fscanf() en stdin (o equivalente, scanf()) los leerá en diferentes líneas si no encuentra el segundo valor en la primera línea que ingresó.

Pero, probablemente, las diferencias más importantes tienen que ver con el manejo de casos de error y la combinación de entrada orientada a líneas y entrada orientada a campos.

Si lee una línea que no puede analizar con sscanf() después de haberlo leído usando fgets() su programa puede simplemente descartar la línea y continuar. Sin embargo, fscanf(), cuando no puede convertir campos, deja toda la entrada en la transmisión. Entonces, si no pudo leer la entrada que deseaba, tendría que ir y leer todos los datos que desea ignorar.

El otro problema sutil entra si quieres mezclar orientado al campo (ala scanf()) con línea orientada (por ejemplo fgets()) llama a su código. Cuándo scanf() convierte un int por ejemplo, dejará atrás un \n en el flujo de entrada (suponiendo que haya uno, como al presionar la tecla Intro), lo que provocará una llamada posterior a fgets() para regresar inmediatamente con solo ese carácter en la entrada. Este es un problema muy común para los nuevos programadores.

Entonces, si bien tiene razón, solo puede usar fscanf() así, es posible que pueda evitar algunos dolores de cabeza usando fgets() + sscanf().

  • Esto es verdad. Por otro lado, agregarías algo de dolor de cabeza porque fgets leería una línea con una longitud máxima que puede causar problemas. Imagine que su búfer tiene 10 caracteres (exagerados solo por ejemplo) y luego se le da una entrada: " 12345\n" que recibirías como " 12" y "345\n". La forma más segura es usar un loop over fgets y realloc para leer una línea completa sin importar su tamaño.

    – Shahbaz

    11 de marzo de 2014 a las 16:58


  • @Shahbaz Todavía soy nuevo en C, pero probé esto y descubrí que “12345” no se divide de la manera que explicaste (“12” y “345\n”). En cambio, obtuve “12345” en la primera variable y un valor negativo de basura en la segunda. ¿Qué punto estabas tratando de hacer? ¿Por qué debería suceder esto?

    – ankush981

    11 de marzo de 2014 a las 17:07

  • Shahbaz: para problemas simples, elija un búfer lo suficientemente grande, lea los primeros caracteres e ignore los demás en un bucle hasta que lea un '\n' 🙂

    – pmg

    11/03/2014 a las 17:10

  • @dotslash, con fgets tienes que dar un búfer acotado. Si hay un número en el límite de ese tamaño, se puede dividir. En su propio ejemplo, intente dar una entrada con 17 espacios seguidos de un número de 6 dígitos.

    – Shahbaz

    11/03/2014 a las 17:21

  • @chux, seguro que puede generar una falla si una línea comienza a volverse ridículamente grande. Lo que digo es que si desea mantener un tamaño máximo, lo cual está bien, debe asegurarse de que la línea que lee se ajuste al búfer, de lo contrario, es posible que los datos no sean confiables y es posible que deba ignore qué parte de la línea ya ha leído y qué parte aún queda. Por cierto, bloquear un programa agotando su memoria no es realmente un ataque. ¡Puede limitar su memoria o enviar una señal KILL o lo que sea! No es un problema de seguridad.

    – Shahbaz

    11 de marzo de 2014 a las 18:02

avatar de usuario
pmg

El problema de usar solo fscanf() es, sobre todo, en la gestión de errores.

Imagina que ingresas “51 años, 85 kg” a ambos programas.

El primer programa falla en el sscanf() y todavía tienes el line para informar errores al usuario, para probar una alternativa de análisis diferente, para algo;

El segundo programa falla en años, age es utilizable, weight es inutilizable.

Recuerde comprobar siempre el valor de retorno de *scanf() para la comprobación de errores.

    fgets(line, sizeof(line), stdin);
    if (sscanf(line, "%d%d", &age, &weight) != 2) /* error with input */;

Editar

Con su primer programa, después del error, el búfer de entrada está limpio; con el segundo programa, el búfer de entrada comienza con AÑO…

La recuperación en el primer caso es fácil; la recuperación en el segundo caso tiene que pasar por algún tipo de borrado del búfer de entrada.

avatar de usuario
chux – Reincorporar a Monica

No hay diferencia entre fscanf() versus fgets()/sscanf() Cuándo:

  1. Los datos de entrada están bien formados.

Se producen dos tipos de errores: E/S y formato. fscanf() maneja simultáneamente estos dos tipos de errores en una sola función, pero ofrece pocas opciones de recuperación. el separado fgets() y sscanf() permitir la separación lógica de los problemas de E/S de los de formato y, por lo tanto, una mejor recuperación.

  1. Solo 1 ruta de análisis con fscanf().

Separación de E/S de escaneo como con fgets/sscanf permite múltiples sscanf() opciones Si una exploración determinada de un búfer no obtiene los resultados deseados, otros sscanf() con diferentes formatos están disponibles.

  1. Sin incrustado '\0'.

rara vez lo hace '\0' ocurre, pero si ocurre uno, sscanf() no lo verá ya que el escaneo se detiene con su ocurrencia, mientras que fscanf() continúa.

En todos los casos, verifique los resultados de las tres funciones.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad