¿Por qué usar fgets() sobre scanf() en C?

Cada vez que usa una función *f , ya sea printf , scanf o sus derivados (fprintf, fscanf, etc.), está haciendo más cosas de las que cree. No sólo estás leyendo (o escribiendo) algo, sino que, y aquí está el problema, lo estás interpretando . La string de formato se puede considerar como una especie de función ‘eval’ como la que vería si programa en Lisp. Entonces, el problema de simplemente leer la entrada del usuario y repetirla es que un actor malévolo puede simplemente insertar un puntero de función o un código ejecutable y listo, ya no tienes el control.

Ventaja de usar scanf():
el usuario no necesita saber el tamaño de la pila , que es el código de inicio de cien bytes. Eso es algo bueno, aunque cualquiera puede simplemente sentarse allí probando strings de entrada más y más largas hasta que ocurra el error BufferOverflow . Idealmente, simplemente escribiría un script que automáticamente intente aumentar el tamaño de las strings y lea el código de salida del programa. Una vez que detecta un error, simplemente regresa a una estimación cercana de la pila. Cualquier sistema operativo moderno que use la aleatorización de direcciones de memoria para hacer que todo el proceso de secuestro de una aplicación sea más difícil, pero de ninguna manera es imposible.

fgets() sobre scanf():

La función fgets es la abreviatura de file-get-string . Recuerde que los archivos pueden ser prácticamente cualquier cosa en los sistemas *nix (sockets, flujos o archivos reales), por lo que podemos usarlos para leer desde la entrada estándar, que nuevamente, técnicamente también es un archivo. Esto también hace que nuestro programa sea más robusto , porque como se menciona en la fuente, simplemente ajuste el código para leer desde la línea de comando, un archivo o stdin, sin muchos problemas.

Fundamentalmente, la función fgets también nos permite especificar una longitud de búfer específica , lo que impide cualquier ataque de desbordamiento de búfer. Si observa el código completo a continuación, notará que se ha definido un tamaño de búfer predeterminado como una macro. Recuerde que C no puede usar variables ‘const int’ para inicializar una array correctamente. Puede hackearlo usando arrays de longitud variable (VLA), pero no es lo ideal y lo recomiendo encarecidamente. Entonces, mientras que en C++ normalmente usaríamos literalmente cualquier otra cosa, aquí usamos macros de preprocesador, pero tenga en cuenta que C y C++ tienen capacidades muy diferentes cuando se trata de su verificación de tipo estático, es decir, que C++ es mejor que C. Por lo tanto fgets() El método puede manejar errores durante la E/S .

Entonces, el código para leer realmente la entrada del usuario es el siguiente:

char* inputBuffer = malloc(sizeof(char) * DEFAULT_BUFFER_SIZE);
memset(inputBuffer, NUL, DEFAULT_BUFFER_SIZE);
  
char* result = NULL;
  
while (result == NULL) {
    result = fgets(inputBuffer, DEFAULT_BUFFER_SIZE, stdin);
  
    if (inputBuffer[strlen(inputBuffer) - 1] != '\n') {
        ErrorInputStringTooLong();
  
        // Since 'result' is the canary
        // we are using to notify of a failure
        // in execution, set it to NULL, to indicate an error.
        // This is a useful value because
        // if for some reason the fgets f/nction were
        // to fail, the return value would also be NULL.
  
        result = NULL;
    }
}

Como era de esperar, asignamos dinámicamente un búfer de tamaño predeterminado. Asignarlo dinámicamente en lugar de asignarlo a la pila nos brinda otra capa de protección contra la destrucción de la pila.

Nota: estamos poniendo a cero explícitamente la memoria en el inputBuffer. C no hace nada por ti, y malloc no es una excepción. La llamada a malloc devuelve un puntero a la primera celda de memoria de la memoria solicitada, pero los valores en cada una de esas celdas no cambian desde antes de la llamada. Para todos los efectos, son basura, así que los ponemos a cero. Tenga en cuenta también que al poner a cero, automáticamente nos damos el terminador nulo, aunque la función fgets en realidad agrega un nulo al final de la string de entrada, siempre que haya suficiente espacio en el búfer.

Cuando el usuario ingresa una string demasiado larga:

Notará que verifico para asegurarme de que el último valor leído no sea una línea nueva. Si eso es cierto, significa que el usuario pasó una string de entrada que era demasiado larga . Para solucionar esto, debemos establecer nuestra variable de ‘resultado’ en NULL para que podamos recorrer el ciclo nuevamente, pero también debemos borrar el búfer de entrada. De lo contrario, el programa simplemente leerá la entrada anterior, que aún no se ha utilizado, en lugar de solicitar al usuario una entrada adicional. Para manejar esto, proporciono dos funciones adicionales.

static inline void ErrorInputStringTooLong()
{
  
    // NOTE: Print to stderr, not to stdout.
    fprintf(stderr, "[ERROR]: The input was too long, please try again.\n");
  
    // Having notified the user,
    // clear the input buffer and return.
    ClearInputBuffer();
}
  
static inline void ClearInputBuffer()
{
  
    // This variable is merely here to munch the garbage out of the input
    // buffer. As long as the input buffer's current character is not either
    // a new line character or the end of input, keep reading characters. This
    // seems to be the only portable way of clearing the input buffer.
    char c = NUL;
  
    while ((c = getchar()) != '\n' && c != EOF) {
        // Do nothing until input buffer fully flushed.
    }
}

Nota: por lo general, para borrar el búfer de entrada, uno simplemente llamaría a fseek(stdin, 0, SEEK_END); pero no parece funcionar en todas las plataformas. Por lo tanto, se utiliza el método descrito anteriormente.

Nota: si fgets devuelve un error , debe llamar a ferror() para averiguar qué salió mal.

Publicación traducida automáticamente

Artículo escrito por jflopezfernandez y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

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