Diferencia entre scanf, gets y fgets

La biblioteca estándar de C provee de varias funciones para introducir datos a nuestros programas. Estas son parte del módulo stdio.h. Vamos a ver la diferencia que existe entre tres de ellas (scanf, gets y fgets). Las tres nos permiten leer cadenas de caracteres introducidas por el usuario pero con importantes diferencias.

scanf es la más versátil de la tres dado que puede leer distintos tipos de datos (cadenas, enteros, reales…) según el formato especificado. En cambio, gets y fgets sólo leen cadenas de caracteres (nótese la ‘s’ final del nombre que hace referencia en este caso a string).

Veamos como leen cada una de ellas la entrada del usuario. Cuando se utiliza la consola, el programa no lee directamente el texto tal cual lo introduce el usuario si no que éste se almacena en una tabla intermedia que llamaremos buffer. Cada vez que el usuario pulsa el retorno de carro, se llena el buffer con la línea introducida (incluyendo el carácter '\n'). Las diferencias radican en cómo leen las tres funciones del buffer. Vamos a verlo con el siguiente ejemplo:

    int i1, i2;
    char s1[30], s2[30];

    scanf("%d", &i1);
    scanf("%d", &i2);

    gets("%s", s1);
    gets("%s", s2);

Supongamos que el usuario introduce “20\n30\npablo\n”, la secuencia de lectura sería la siguiente:

Entrada Buffer antes Instrucción Buffer después
20\n 20\n scanf("%d", &i1); \n
30\n \n30\n scanf("%d", &i2); \n
\n \n gets("%s", &s1);
pablo\n pablo\n gets("%s", &s2);

El primer scanf tiene que leer del buffer hasta que encuentra un número (%d). En cuanto encuentra el 20 termina de leer, y deja en el buffer un '\n'. El segundo scanf, tras la entrada del usuario, se encuentra con \n30\n, y tiene que realizar la misma tarea que el anterior, leer un número. Se salta el primer '\n', y lee 30, dejando de nuevo un '\n' en el buffer. La función gets es más simple, y lo único que hace es leer todo lo que haya en el buffer hasta que encuentre un '\n', y lo copia en la variable correspondiente. Así, el primer gets se encuentra un \n, lo consume pero no copia nada en s1. El segundo gets se encuentra pablo\n, así que lee todo lo que hay en el buffer y lo guarda en s2. Para permitir que pablo se copie en s1, una posibilidad es incluir una llamada a getchar() antes de emplear gets para leer s1. Esta función lee un carácter del buffer, consumiendo así el '\n' que impedía rellenar s1 con pablo.

Ahora bien, gets es una función insegura tal como lo indica el compilador (warning: this program uses gets(), which is unsafe.). El problema es que no puedes controlar el número de caracteres que introduce el usuario pudiendo ocurrir que se copien en la cadena más caracteres que los permitidos por su tamaño máximo. Por ejemplo, en el código anterior que el usuario introdujera más de 29 caracteres. Este comportamiento puede producir que el programa termine abruptamente. Su uso también puede producir fallos graves de seguridad al habilitar la posibilidad de ejecutar código malicioso.

La alternativa segura de gets es fgets que si permite establecer el máximo de caracteres que pueden leerse. Un ejemplo de uso sería:

    char s[30];

    fgets(s, 30, stdin);

Primero se establece donde se quiere copiar la línea leída. A continuación el máximo número de caracteres incluyendo el '' que se pueden leer. En el ejemplo, fgets lee del buffer hasta que encuentra un '\n' o hasta que haya copiado en s un máximo de 29 caracteres. La propia fgets se encarga de incluir el '' para finalizar la cadena. El última parámetro hace referencia de donde se obtiene los datos. Al igual que otras funciones como fprintf, fgets se puede emplear para leer de la consola, indicándolo con stdin (standard input), o de un fichero. Otra diferencia importante con gets es que el retorno de carro se copia también en la cadena.