Saltar al contenido

Desventajas de scanf

Solución:

Los problemas con scanf son (como mínimo):

  • utilizando %s para obtener una cadena del usuario, lo que conduce a la posibilidad de que la cadena sea más larga que su búfer, provocando un desbordamiento.
  • la posibilidad de que un escaneo fallido deje el puntero de su archivo en una ubicación indeterminada.

Prefiero mucho usar fgets para leer líneas completas para que pueda limitar la cantidad de datos leídos. Si tienes un búfer de 1K y lees una línea con fgets puede saber si la línea era demasiado larga por el hecho de que no hay un carácter de nueva línea de terminación (a pesar de la última línea de un archivo sin una nueva línea).

Luego puede quejarse con el usuario o asignar más espacio para el resto de la línea (continuamente si es necesario hasta que tenga suficiente espacio). En cualquier caso, no hay riesgo de desbordamiento del búfer.

Una vez que haya leído la línea, saber que está posicionado en la siguiente línea para que no haya ningún problema allí. Entonces puedes sscanf su cadena al contenido de su corazón sin tener que guardar y restaurar el puntero del archivo para volver a leerlo.

Aquí hay un fragmento de código que utilizo con frecuencia para garantizar que no se desborde el búfer cuando le pido información al usuario.

Podría ajustarse fácilmente para usar un archivo que no sea la entrada estándar si es necesario y también podría hacer que asigne su propio búfer (y seguir incrementándolo hasta que sea lo suficientemente grande) antes de devolvérselo a la persona que llama (aunque la persona que llama entonces sería responsable por liberarlo, por supuesto).

#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 `` 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] = '';
    return OK;
}

Y, un controlador de prueba para ello:

// 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 inputn");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]n", buff);
        return 1;
    }

    printf ("OK [%s]n", buff);

    return 0;
}

Finalmente, una ejecución de prueba para mostrarlo en acción:

$ printf "" | ./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]

La mayoría de las respuestas hasta ahora parecen centrarse en el problema del desbordamiento del búfer de cadenas. En realidad, los especificadores de formato que se pueden utilizar con scanf soporte de funciones explícito ancho de campo , que limitan el tamaño máximo de la entrada y evitan el desbordamiento del búfer. Esto hace que las acusaciones populares de peligros de desbordamiento de búfer de cadenas estén presentes en scanf prácticamente sin fundamento. Reclamando eso scanf es de alguna manera análogo a gets al respecto es completamente incorrecto. Existe una gran diferencia cualitativa entre scanf y gets: scanf proporciona al usuario funciones de prevención de desbordamiento de búfer de cadena, mientras que gets no lo hace.

Se puede argumentar que estos scanf Las características son difíciles de usar, ya que el ancho del campo tiene que estar incrustado en la cadena de formato (no hay forma de pasarlo a través de un argumento variado, ya que se puede hacer en printf). Eso es realmente cierto. scanf de hecho, está bastante mal diseñado en ese sentido. Sin embargo, cualquier afirmación de que scanf está irremediablemente roto con respecto a la seguridad de desbordamiento de búfer de cadena son completamente falsos y generalmente hechos por programadores perezosos.

El verdadero problema con scanf tiene una naturaleza completamente diferente, aunque también se trata de Desbordamiento. Cuando scanf La función se utiliza para convertir representaciones decimales de números en valores de tipos aritméticos, no proporciona protección contra el desbordamiento aritmético. Si ocurre un desbordamiento, scanf produce un comportamiento indefinido. Por esta razón, la única forma correcta de realizar la conversión en la biblioteca estándar de C son las funciones de strto... familia.

Entonces, para resumir lo anterior, el problema con scanf es que es difícil (aunque posible) de usar de manera adecuada y segura con los búferes de cadena. Y es imposible utilizarlo de forma segura para la entrada aritmética. Este último es el verdadero problema. Lo primero es solo un inconveniente.

PD Lo anterior pretende ser sobre toda la familia de scanf funciones (incluyendo también fscanf y sscanf). Con scanf Específicamente, el problema obvio es que la idea misma de usar una función estrictamente formateada para leer potencialmente interactivo la entrada es bastante cuestionable.

De las preguntas frecuentes de comp.lang.c: ¿Por qué todos dicen que no deben usar scanf? ¿Qué debo usar en su lugar?

scanf tiene varios problemas; consulte las preguntas 12.17, 12.18ay 12.19. Además, es %s formato tiene el mismo problema que gets() tiene (consulte la pregunta 12.23): es difícil garantizar que el búfer de recepción no se desborde. [footnote]

Más generalmente, scanf está diseñado para una entrada formateada relativamente estructurada (de hecho, su nombre se deriva de “formato de escaneo”). Si presta atención, le dirá si tuvo éxito o fracasó, pero solo puede decirle aproximadamente dónde falló, y no cómo ni por qué. Tiene muy pocas oportunidades de realizar una recuperación de errores.

Sin embargo, la entrada interactiva del usuario es la entrada menos estructurada que existe. Una interfaz de usuario bien diseñada permitirá la posibilidad de que el usuario escriba casi cualquier cosa, no solo letras o puntuación cuando se esperaban dígitos, sino también más o menos caracteres de los esperados, o ningún carácter (es decir, solo la tecla RETURN), o EOF prematuro, o cualquier cosa. Es casi imposible lidiar con elegancia con todos estos problemas potenciales al usar scanf; es mucho más fácil leer líneas completas (con fgets o similares), luego interpretarlos, ya sea usando sscanf o algunas otras técnicas. (Funciones como strtol, strtok, y atoi a menudo son útiles; consulte también las preguntas 12.16 y 13.6). scanf variante, asegúrese de verificar el valor de retorno para asegurarse de que se encontró el número esperado de elementos. Además, si usa %s, asegúrese de protegerse contra el desbordamiento del búfer.

Tenga en cuenta, por cierto, que las críticas a scanf no son necesariamente acusaciones de fscanf y sscanf. scanf lee de stdin, que suele ser un teclado interactivo y, por lo tanto, es el menos restringido, lo que genera la mayoría de los problemas. Cuando un archivo de datos tiene un formato conocido, por otro lado, puede ser apropiado leerlo con fscanf. Es perfectamente apropiado analizar cadenas con sscanf (siempre que se verifique el valor de retorno), porque es muy fácil recuperar el control, reiniciar el escaneo, descartar la entrada si no coincide, etc.

Enlaces adicionales:

  • explicación más larga de Chris Torek
  • una explicación más larga por parte de los tuyos de verdad

Referencias: K & R2 Sec. 7,4 p. 159

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *