Comportamiento de la función fgets(lenguaje C).

Iniciado por NOB2014, 23 Marzo 2016, 05:02 AM

0 Miembros y 1 Visitante están viendo este tema.

NOB2014

Hola.

fgets( word[i], MAX_COL , stdin );

if((p=strchr(word[i], '\n'))){
*p='\0';
}
else{
while((ch = getchar()) !='\n' && ch!=EOF);
word[i][MAX_COL] = '\0';
}



En este trozo de código está mi gran duda, según lo que tengo leído es que si ingreso menos caracteres que  MAX_COL fgets los guarda desde el buffer de teclado en la variable indicada, en este caso word y también el carácter de nueva línea (\n) que se produce al apretar < Enter >, la primera duda, ¿es estrictamente necesario remplazar el carácter \n por \0? -
En el caso de ingresar más caracteres del establecido por MAX_COL fgets lee  MAX_COL – 1 y en la última casilla de memoria asignada a MAX_COL copia el carácter \n, de ser esto cierto la otra duda es:  ¿la sentencia else no se ejecuta nunca en este caso, o estoy equivocado?

Es todo y desde ya muchas gracias por el tiempo que le dediquen al tema,-

Saludos.
Daniel

abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-

MAFUS

#1
Sobre la 1ª pregunta: Cuándo guardas un dato, eso es un nombre, una dirección, etc. éstos no tienen implícitos cambios de línea Es de buena práctica separar los datos de su presentación: el dato es la cadena, la presentación dicta si debe haber un salto de línea o no. Por consiguiente sí, es bueno quitar el salto de línea.

Sobre la 2ª pregunta: fgets copia los N primeros caracteres teniendo en cuenta siempre que el último carácter de la cadena destino será '\0' así que en verdad se copian N-1 caracteres; de esta forma fgets se asegura que en el destino hay una cadena con su final. Los caracteres que no fueron copiados se quedan en el búffer del teclado.

NOB2014

Hola, MAFUS.
Ahora si me que más claro, por lo menos la primer pregunta, en cuanto a la segunda no logro interpretarte demasiado, no obstante, hice mis propias pruebas y en la práctica pasa lo siguiente:
si por Ej. la cadena soporta 10 caracteres, si ingreso 9 o menos se ejecuta la línea que está a continuación del if, en cambio si ingreso 10 o más caracteres se ejecutan las líneas que estan a continuación del else, o sea en la práctica todo funciona correctamente, en la teoria no me queda claro lo siguiente:
De las dos formas (ingrece menos de 10 o ingrece mas de 9 ) el caracter \n va a estar siempre y como está escrito el programa nunca deberia ejecutarse la sentencia else.-
espero encontrar la explicación a esto.- 

Saludos.
Daniel
abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-

MAFUS

Te voy a mostrar una serie de programa de pruebas que te mostrarán el funcionamiento de fgets y lo que ocurre con el buffer stdin.

Es una buena idea realizar estos programas cuándo estudias programación para saber que ocurre en cada situación.

Este primero te enseñará qué copia desde stdin a tu buffer cuando le das a fgets un tamaño inferior al tamaño de tu buffer. En este caso tenemos un buffer de 5 elementos y fgets solo puede copiar 3.


#include <stdio.h>
#include <ctype.h>

#define MAX_BUFF 5

void representar_char(int c) {
    printf("%i", c);
    if(isalnum(c))
        printf(" -> \'%c\'", c);
    else {
        switch(c) {
            case '\0':
                printf("  -> \'\\0\'");
                break;
            case '\n':
                printf(" -> \'\\n\'");
                break;
        }
    }
}
   
int main() {
    char mi_buffer[MAX_BUFF];
    int i;
    printf(">> ");
    fgets(mi_buffer, 3, stdin);
    for(i = 0; i < MAX_BUFF; ++i) {
        printf("mi_buffer[%i] : ", i);
        representar_char(mi_buffer[i]);
        putchar('\n');
    }
   
    return 0;
}


Si al ejecutar de damos como cadena de entrada el carácter 'A' obtenemos esta salida:


>> A
mi_buffer[0] : 65 -> 'A'
mi_buffer[1] : 10 -> '\n'
mi_buffer[2] : 0  -> '\0'
mi_buffer[3] : -86
mi_buffer[4] : -2


Vemos que en primer lugar se guarda el carácter 'a', seguidamente el carácter de nueva línea '\n' y finalmente el caracter nulo '\0'. A partir del cuarto elemento, mi_buffer[3], lo que hay es basura.

Ahora, si le das como entrada la cadena "AB" verás el siguiente resultado:


>> AB
mi_buffer[0] : 65 -> 'A'
mi_buffer[1] : 66 -> 'B'
mi_buffer[2] : 0  -> '\0'
mi_buffer[3] : -80
mi_buffer[4] : -3


Como puedes ver no se ha guardado en el mi_buffer el carácter de nueva línea, que sigue existiendo en el buffer stdin, como veremos más adelante. Para corroborar que el último carácter a guardar siempre será un '\0' puedes repetir el programa esta vez con la cadena "ABC".


>> ABC
mi_buffer[0] : 65 -> 'A'
mi_buffer[1] : 66 -> 'B'
mi_buffer[2] : 0  -> '\0'
mi_buffer[3] : -13
mi_buffer[4] : -2


Ahora, para ver que queda en el buffer hay que modificar un poco el programa anterior.

#include <stdio.h>
#include <ctype.h>

#define MAX_BUFF 5

void representar_char(int c) {
    printf("%i", c);
    if(isalnum(c))
        printf(" -> \'%c\'", c);
    else {
        switch(c) {
            case '\0':
                printf("  -> \'\\0\'");
                break;
            case '\n':
                printf(" -> \'\\n\'");
                break;
        }
    }
}
   
int main() {
    char mi_buffer[MAX_BUFF];
    int i;
    char c;
    printf(">> ");
    fgets(mi_buffer, 3, stdin);
    for(i = 0; i < MAX_BUFF; ++i) {
        printf("mi_buffer[%i] : ", i);
        representar_char(mi_buffer[i]);
        putchar('\n');
    }
    while((c = getchar()) != '\n') {
        printf("caracter en stdin : ");
        representar_char(c);
        putchar('\n');
    }
    printf("caracter en stdin : ");
    representar_char(c);
    putchar('\n');
    return 0;
}


Si le damos como cadena de entrada "ABCDEFGHI" da el siguiente resultado:

>> ABCDEFGHI
mi_buffer[0] : 65 -> 'A'
mi_buffer[1] : 66 -> 'B'
mi_buffer[2] : 0  -> '\0'
mi_buffer[3] : -79
mi_buffer[4] : -1
caracter en stdin : 67 -> 'C'
caracter en stdin : 68 -> 'D'
caracter en stdin : 69 -> 'E'
caracter en stdin : 70 -> 'F'
caracter en stdin : 71 -> 'G'
caracter en stdin : 72 -> 'H'
caracter en stdin : 73 -> 'I'
caracter en stdin : 10 -> '\n'


mi_buffer sigue recibiendo tantos caracteres como le hemos dicho a fgets, ver que uno de ellos es el carácter '\0' pues debe crear una cadena, lo demás se encuentra esperando en el buffer stdin.

Por último hay que ver que pasa si a fgets se le pasa un tamaño a copiar mayor que el tamaño de buffer de destino y la cadena a copiar también es mayor que el destino. Para ello modificamos de nuevo el programa:


#include <stdio.h>
#include <ctype.h>

#define MAX_BUFF 5

void representar_char(int c) {
    printf("%i", c);
    if(isalnum(c))
        printf(" -> \'%c\'", c);
    else {
        switch(c) {
            case '\0':
                printf("  -> \'\\0\'");
                break;
            case '\n':
                printf(" -> \'\\n\'");
                break;
        }
    }
}
   
int main() {
    char mi_buffer[MAX_BUFF];
    int i;
    char c;
    printf(">> ");
    fgets(mi_buffer, MAX_BUFF + 2, stdin);
   
    /* Qué hay dentro de mi_buffer */
    for(i = 0; i < MAX_BUFF; ++i) {
        printf("mi_buffer[%i] : ", i);
        representar_char(mi_buffer[i]);
        putchar('\n');
    }
   
    /* Cuidado aquí, esta parte de la memoria está fuera de todo contro */
    for(i = MAX_BUFF; i < MAX_BUFF + 2; ++i) {
        printf("*** mi_buffer[%i] : ", i);
        representar_char(mi_buffer[i]);
        putchar('\n');
    }
       
    /* Qué hay en stdin */
    while((c = getchar()) != '\n') {
        printf("caracter en stdin : ");
        representar_char(c);
        putchar('\n');
    }
    printf("caracter en stdin : ");
    representar_char(c);
    putchar('\n');
    return 0;
}


Al pasarle la cadena ABCDEFGHIJKL al programa obtenemos:


>> ABCDEFGHIJKL
mi_buffer[0] : 65 -> 'A'
mi_buffer[1] : 66 -> 'B'
mi_buffer[2] : 67 -> 'C'
mi_buffer[3] : 68 -> 'D'
mi_buffer[4] : 69 -> 'E'
*** mi_buffer[5] : 70 -> 'F'
*** mi_buffer[6] : 0  -> '\0'
caracter en stdin : 71 -> 'G'
caracter en stdin : 72 -> 'H'
caracter en stdin : 73 -> 'I'
caracter en stdin : 74 -> 'J'
caracter en stdin : 75 -> 'K'
caracter en stdin : 76 -> 'L'
caracter en stdin : 10 -> '\n'


Se puede ver que fgets sigue cumpliendo su trabajo, copia tantos caracteres como le hemos dicho, ha desbordado mi_buffer, pero trabaja como se espera de él. Los datos en marcados en *** están fuera de todo control y podrían darse otras situaciones como el cierre del programa, cuelgue del sistema, etc.
Para terminar se sigue viendo en en stdin está el resto de la cadena que no se ha copiado.

NOB2014

#4
Hola, MAFUS.
ahora sí que queda todo clarísimo ;-) ;-) ;-), gracias por el tiempo que le dedicaste y te debo una cerveza con maníes. -

Saludos.
Daniel


abraza las cosas y personas malas como si fueran tu mas preciada joya,Son tus mas grandes maestros de paciencia sabiduría y amor y cuando lo abrazas dejan de causar dolor.-