problemas con fseeks

Iniciado por m@o_614, 30 Enero 2014, 21:55 PM

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

m@o_614

Saludos, tengo problemas con unos fseeks que tengo dentro de un ciclo while que me lee un archivo, pero a la hora de imprimir el archivo no lo hace correctamente

este es el codigo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 8

typedef enum {INS,OP,DIR,MAQ,CALCULADO,CALCULAR,TOTAL} tabla;

void buscarFinLinea(FILE *hc12);
void listaTABOP();
char *Tabla_Operandos(FILE *hc12,int tabla);
void ignorarEspacios(FILE *hc12);
void quitarSaltosLinea(char *cadena);

int main()
{
   listaTABOP();
   return 0;
}

void buscarFinLinea(FILE *hc12)
{
   int car;
   while((car = fgetc(hc12))!= '\n')
       ;
}

void quitarSaltosLinea(char *c)
{
   char *ptr;
   if(((ptr = strchr(c,'\n'))!= NULL)||((ptr = strchr(c,'\t'))!= NULL)||((ptr = strchr(c,' '))!= NULL))
      *ptr = '\0';
}

void listaTABOP()
{
   int car,i,pos,n;
   FILE *hc12;
   COD *ultimo = NULL;
   char *ins,*op,**dir,**maq,**cal,**x_cal,**sum;
   if((hc12 = fopen("Tabla_OP.txt","r"))!= NULL)
   {
       while((car = fgetc(hc12))!= EOF)
       {
           i = 0;
           fseek(hc12,-1,SEEK_CUR);
           ins = Tabla_Operandos(hc12,INS);
           printf("[%s]\t",ins);
           ignorarEspacios(hc12);
           op = Tabla_Operandos(hc12,OP);
           printf("[%s]",op);
           pos = ftell(hc12);
           buscarFinLinea(hc12);
           if((car = fgetc(hc12)) != '\t')
           {
               n = 0;
               fseek(hc12,pos,SEEK_SET);
               ignorarEspacios(hc12);
               dir = (char**)malloc(sizeof(char*));
               dir[i] = Tabla_Operandos(hc12,DIR);
               printf("\t[%s]\t",dir[i]);
               ignorarEspacios(hc12);
               maq = (char**)malloc(sizeof(char*));
               maq[i] = Tabla_Operandos(hc12,MAQ);
               printf("[%s]\t",maq[i]);
               ignorarEspacios(hc12);
               cal = (char**)malloc(sizeof(char*));
               cal[i] = Tabla_Operandos(hc12,CALCULADO);
               printf("[%s]\t",cal[i]);
               ignorarEspacios(hc12);
               x_cal = (char**)malloc(sizeof(char*));
               x_cal[i] = Tabla_Operandos(hc12,CALCULAR);
               printf("[%s]\t",x_cal[i]);
               ignorarEspacios(hc12);
               sum = (char**)malloc(sizeof(char*));
               sum[i] = Tabla_Operandos(hc12,TOTAL);
               printf("[%s]\n",sum[i]);
               buscarFinLinea(hc12);
           }
           else
           {
               n = 1;
               fseek(hc12,pos,SEEK_SET);
               dir = (char**)malloc(MAX*sizeof(char*));
               maq = (char**)malloc(MAX*sizeof(char*));
               cal = (char**)malloc(MAX*sizeof(char*));
               x_cal = (char**)malloc(MAX*sizeof(char*));
               sum = (char**)malloc(MAX*sizeof(char*));
               do
               {
                   ignorarEspacios(hc12);
                   dir[i] = Tabla_Operandos(hc12,DIR);
                   printf("\t[%s]\t",dir[i]);
                   ignorarEspacios(hc12);
                   maq[i] = Tabla_Operandos(hc12,MAQ);
                   printf("[%s]\t",maq[i]);
                   ignorarEspacios(hc12);
                   cal[i] = Tabla_Operandos(hc12,CALCULADO);
                   printf("[%s]\t",cal[i]);
                   ignorarEspacios(hc12);
                   x_cal[i] = Tabla_Operandos(hc12,CALCULAR);
                   printf("[%s]\t",x_cal[i]);
                   ignorarEspacios(hc12);
                   sum[i] = Tabla_Operandos(hc12,TOTAL);
                   printf("[%s]\n",sum[i]);
                   buscarFinLinea(hc12);
                   i++;
                   printf("\t");
               }while((car = fgetc(hc12)) == '\t');
           }
       }
   }
   else
      printf("No se pudo abrir el archivo");
}

void ignorarEspacios(FILE *hc12)
{
   int car;
   do
   {
       car = fgetc(hc12);
   }while(car == '\t' || car == ' ');
}

char *Tabla_Operandos(FILE *hc12,int tabla)
{
   int car,lon = 0,pos;
   char *c;
   fseek(hc12,-1,SEEK_CUR);
   pos = ftell(hc12);
   if((tabla==INS)||(tabla==OP)||(tabla==DIR)||(tabla==MAQ)||(tabla==CALCULADO)||(tabla==CALCULAR))
   {
       do
       {
           car = fgetc(hc12);
           lon++;
       }while(car != '\t' && car != EOF);
   }
   else
   {
       do
       {
           car = fgetc(hc12);
           lon++;
       }while(car != '\n' && car != EOF);
       lon--;
   }
   fseek(hc12,pos,SEEK_SET);
   c = (char*)calloc((lon+1),sizeof(char));
   fgets(c,lon+1,hc12);
   quitarSaltosLinea(c);
   return c;
}

[
y el archivo es
quote]ABA         NO   INH      1806      2   0   2
         ADCA   SI   IMM      89ii      1   1   2
                            DIR      99dd      1   1   2
                            EXT      B9hhll   1   2   3
                            IDX      A9xb      1   1   2
                            IDX1      A9xbff   1   2   3
                            IDX2      A9xbeeff   1   3   4
                            [D,IDX]   A9xb      1   1   2
                            [IDX2]   A9xbeeff   1   3   4[/quote]
el problema es que  cuando llega al codop ADCA no me imprime toda la linea solo me imprime

[]....[ADCA]...[SI]...[IMM]...[89ii]....[1]...[1]

esto se arregla si le quito el primer fseek() que aparece en el while() de la funcion listaTABOP, pero si se lo quito ahora los codops siguientes al ADCA los imprime sin la primer letra, se la come,entonces como puedo arreglar este problema

gracias

x64core

¿Has depurado la aplicación?

m@o_614

Saludos x64Core el problema es que no se usar el depurador de codeblocks :S lo intente con un tutorial pero no pude

x64core

Cita de: m@o_614 en 31 Enero 2014, 18:32 PM
Saludos x64Core el problema es que no se usar el depurador de codeblocks :S lo intente con un tutorial pero no pude
Pues te recomiendo que empieces a aprender a usarlo usar los depuradores para encontrar errores en tus programas es tan
indispensable y realmente no es nada dificil.

Yoel Alejandro

#4
Bueno x64Core particularmente no soy partidario de usar depuradores o cosas por es estilo, prefiero ir "a manita limpia", y me ha funcionado bien porque así ejercitas más el cerebro, jeje :laugh:

El problema al parecer es que te mueves para adelante y para atrás en el archivo, abusando de fseek. Un buen programador debe ser cauto, debe simplemente evitar andar por terrenos peligrosos, y el estilo de tu programación está siendo definitivamente muy vulnerable. Cualquier error o descuido y tienes un comportamiento completamente impredecible.

Usa el principio de "divide y vencerás". Ve, hazlo así. Coje una línea del archivo txt, y la pasas a una función que analice sintácticamente para detectar sus divisiones en columnas con el carácter tabulador, es decir, lo que hace tablaOperandos. Pero que tablaOperandos y listaTABOP NO SE MUEVAN juntas por el mismo archivo. Eso NUNCA se hace, cada función debe tener su espacio de trabajo "privado". ¿Entiendes la filosofía subyacente?

Entonces, vamos a hacer una función que vaya cogiendo una línea del archivo, y la copie a un arreglo tipo búfer, y se la pase a tablaOperandos, así esta última lee del búfer y no se mete para nada con el archivo original (reduciendo el riesgo de conflictos).

En tu código original tenías un problema porque tablaOperandos al leer la línea avanzaba el indicador de posición de archivo, luego tenías que devolverlo a su posición original con [ttfseek[/tt], etc ....  muy engorroso. Con esta solución no tienes que hacer nada de eso:



#define CR 13
#define LF 10

void readLine ( FILE* hc12, char *buffer ) {

cchar c;
int i;

i = 0;
while ( !feof(hc12) && (c = fgetc(hc12)) != CR && c != LF )  
buffer[i++] = c;

buffer[i] = '\0';

/* avanzamos hasta que no haya CR ni LF */
while ( ( c = fgetc(hc12) ) == CR || c == LF )
;
/* devolvemos el último carácter leído */
if ( !feof(hc12) ) ungetc(c, hc12);
}


y listo, agarras ese buffer y se lo pasas a tablaOperandos. Claro, antes debes haber definido el buffer como un arreglo con la suficiente longitud para contener cualquier posible línea del archivo (no se, 100 caracteres quizás). Ahora acomoda tablaOperandos para que trabaje sobre un arreglo y no sobre un fichero.

Te explico la última parte del código antes mostrado. En ficheros de texto el fin de línea se hace con una sucesión de dos caracteres, generalmente el <i>carriage return</i> CR seguido de <i>linefeed</i> LF. Entonces, si alcanzas digamos el CR, pues cierras el búfer. Pero al invocar el nuevo la función, leerá el LF y cerrará el búfer ... ¡vacío! O sea, que después de una llamada exitosa a readLinea la próxima llamada que ejecutes devolverá un búfer vacío. Para evitar eso, se bede avanzar el indicador de posición de archivo hasta no encontrar ni CR ni LF, luego de cerrar el búfer de línea. Es lo que hace el:


/* avanzamos hasta que no haya CR ni LF */
while ( ( c = fgetc(hc12) ) == CR || c == LF )
;
/* devolvemos el último carácter leído */
if ( !feof(hc12) ) ungetc(c, hc12);


Te voy a explicar algo, el uso de ungetc(). Supón que tienes un fichero así:


Pedro
Maria


entonces empieza a leer la línea Pedro, hasta que haya la secuencia CR + LF. Primero lee CR, entonces sabe que debe continuar hasta que no haya ni CR ni LF. Así leee CR, luego LF, luego la M inicial de "Maria". Ahí sabe que se terminó la primera línea. Pero hemos agarrado la 'M', luego tenemos que "devolverla" al fichero, por eso usamos ungetc. Es algo parecido a lo que hacías con fseek( hc12, -1, SEEK_CUR ).
   
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

Yoel Alejandro

#5
Continuando con el tema, ahora queremos una función que tome un búfer de línea del archivo, y sepa encontrar los distintos campos separados por espacios. Aquí la tienes, su código es breve pero maneja unos conceptos un poquito complicados de apuntadores, como por ejemplo el apuntador pasado por referencia o puntero-a-puntero del tipo char **. Si no lo entiendes me lo preguntas luego y te lo explico bien.:

Código (cpp) [Seleccionar]

/* Lee un campo del búfer, considerando un campo como toda sucesión de
  caracteres de no espacio. Devuelve NULL si no hay más campos que leer */
char * readField( char ** buffer_ptr ) {

int i;
char c;
char * buffer = *buffer_ptr;

if ( *buffer == '\0' ) return NULL;

/* elimina los espacios iniciales */
while ( *buffer == ' ' || *buffer == '\t' )
buffer++;

/* ahora toma la porción inicial de la cadena hasta encontrar
  espacio o tabulador */
i = 0;
while ( (c = buffer[i]) != '\0' && c != ' ' && c != '\t' )
i++;

/* finaliza la sub-cadena donde halló el carácter espacio o tabulador */
buffer[i] = '\0';

/* y si hay más sub-cadenas que leer, avanza el búfer */
if ( c != '\0' )
*buffer_ptr = buffer + i + 1;
else
*buffer_ptr = buffer + i;

if ( i > 0 )
return buffer; /* leyó al menos un carácter */
else
return NULL; /* no leyó nada */
}
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

Yoel Alejandro

#6
Ok, ahora vamos a poner todo junto, a mí me salió en apenas 89 líneas de código. Y creé un fichero de texto de prueba con el mismo contenido que tú dices, y me imprimió la tabla perfecta ;D

Es cuestión de ser hábil combinando las cosas, así puedes lograr mucho con poco ... Bueno, espero te sirva, aquí tienes el fuente completo con el main() y todo, sólo de copiar, pegar, compilar y probar.

Código (cpp) [Seleccionar]

#include <stdlib.h>
#include <stdio.h>

#define CR 10
#define LF 13

typedef enum {INS, OP, DIR, MAQ, CALCULADO, CALCULAR, TOTAL} tabla;

void readLine ( FILE *, char * );
char * readField( char ** );

int main()
{
char * buffer, * old_buffer;
char * field;
FILE * hc12;

if ( ( buffer = (char *) malloc( 101 * sizeof(char) ) ) == NULL ) return -1;
if ( ( field = (char *) malloc( 101 * sizeof(char) ) ) == NULL ) return -1;

if ( ( hc12 = fopen( "file1.txt", "r") ) == NULL ) return -1;

while ( !feof( hc12) ) {
readLine( hc12, buffer);
old_buffer = buffer;
while ( ( field = readField( &buffer ) ) != NULL ) {
printf("%s\t", field);
}
printf("\n");
buffer = old_buffer;
}

fclose( hc12 );

return 0;
}

void readLine ( FILE *hc12, char *buffer ) {

char c;
int i;

i = 0;
while ( !feof(hc12) && (c = fgetc(hc12)) != CR && c != LF )  
buffer[i++] = c;

buffer[i] = '\0';

/* avanzamos hasta que no haya CR ni LF */
while ( ( c = fgetc(hc12) ) == CR || c == LF )
;
/* devolvemos el último carácter leído */
if ( !feof(hc12) ) ungetc(c, hc12);
}

/* Lee un campo del búfer, considerando un campo como toda sucesión de
  caracteres de no espacio. Devuelve NULL si no hay más campos que leer */
char * readField( char ** buffer_ptr ) {

int i;
char c;
char * buffer = *buffer_ptr;

if ( *buffer == '\0' ) return NULL;

/* elimina los espacios iniciales */
while ( *buffer == ' ' || *buffer == '\t' )
buffer++;

/* ahora toma la porción inicial de la cadena hasta encontrar
  espacio o tabulador */
i = 0;
while ( (c = buffer[i]) != '\0' && c != ' ' && c != '\t' )
i++;

/* finaliza la sub-cadena donde halló el carácter espacio o tabulador */
buffer[i] = '\0';

/* y si hay más sub-cadenas que leer, avanza el búfer */
if ( c != '\0' )
*buffer_ptr = buffer + i + 1;
else
*buffer_ptr = buffer + i;

if ( i > 0 )
return buffer; /* leyó al menos un carácter */
else
return NULL; /* no leyó nada */
}


Pruébalo y me dices!!

==========================================
Por cierto y aparte, noté que tienes en el código original una sentencia que no entiendo bien:
Código (cpp) [Seleccionar]

dir = (char**)malloc(sizeof(char*));
dir[i] = Tabla_Operandos(hc12,DIR);

Ok, ve. Con el malloc defines un arreglo dir de UN único elemento del tipo puntero a char, que luego rellenas con el string que produce la función Tabla_Operandos. Ahora, dos preguntas: ¿Dónde reservas el espacio de la memoria para los restantes elementos del arreglo dir, aparte del primero?
Además, si dir contendrá un string, ¿dónde reservas la memoria para dicho arreglo de caracteres? Eso tiene que hacerse de la siguiente manera:

Código (cpp) [Seleccionar]

/* Reservando el arreglo dir como arreglo de cadenas */
/ *  (dir_length es la longitud que esperas para dir ) */
dir = (char **) malloc( dir_length * sizeof (char *) );

/* ahora se reserva memoria para cada string dentro del arreglo */
for ( i = 0; i < dir_length; i++ )
   dir[i] = (char *) malloc( s_length * sizeof(char) );
/* donde s_length es la longitud deseada para cada string */


De hecho, no se cómo el programa no te produjo un error de "violación de segmento" en tiempo de ejecución ... :o

De, hecho, cada vez que se usa malloc uno tiene que comprobar si la asignación de memoria se produjo con éxito (casi siempre así sucede, pero no se puede dar por sentado). Si no se pudo asignar memoria, se debe salir del programa:

Código (cpp) [Seleccionar]

/* Reservando el arreglo dir como arreglo de cadenas */
/ *  (dir_length es la longitud que esperas para dir ) */
dir = (char **) malloc( dir_length * sizeof (char *) );
if ( dir == NULL ) return -1;

/* ahora se reserva memoria para cada string dentro del arreglo */
for ( i = 0; i < dir_length; i++ ) {
   dir[i] = (char *) malloc( s_length * sizeof(char) );
   if ( dir[i] == NULL ) return -1;
}

y esto te evitará cualquier "comportamiento inesperado" del programa, para que no te pase como a Windows que se cuelga a cada rato jeje.

Saludos, y quedo a la orden para cualquier cosa .... Yoel

Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

rir3760

Cita de: yoel_alejandro en  5 Febrero 2014, 01:57 AMEl problema al parecer es que te mueves para adelante y para atrás en el archivo, abusando de fseek.
Eso es algo que ya se recomendó a m@o_614 en otros temas pero por alguna razón (valida o no, no lo se) simplemente no la sigue.

Cita de: yoel_alejandro en  5 Febrero 2014, 01:57 AMTe explico la última parte del código antes mostrado. En ficheros de texto el fin de línea se hace con una sucesión de dos caracteres, generalmente el <i>carriage return</i> CR seguido de <i>linefeed</i> LF. Entonces, si alcanzas digamos el CR, pues cierras el búfer. Pero al invocar el nuevo la función, leerá el LF y cerrará el búfer ... ¡vacío! O sea, que después de una llamada exitosa a readLinea la próxima llamada que ejecutes devolverá un búfer vacío. Para evitar eso, se bede avanzar el indicador de posición de archivo hasta no encontrar ni CR ni LF, luego de cerrar el búfer de línea.
No es necesario. Cuando se abre un archivo en modo texto (como es nuestro caso) las funciones de la biblioteca estándar convierten el carácter (o caracteres) que indican un avance de linea a '\n' de forma transparente. El proceso opuesto (convertir '\n' a lo que sea indique un avance de linea) también aplica.

Esa es una de las razones por las cuales no se recomienda el uso de fseek con streams en modo texto: no se puede garantizar que el numero de bytes coincida con el numero de caracteres.

Ya que la aproximación es leer la linea descartando el carácter '\n' la función se puede reducir a:
void readLine(FILE *in, char *line)
{
   int ch;

   while ((ch = fgetc(in)) != EOF && ch != '\n')
      *line++ = ch;
   *line = '\0';
}

Se puede mejorar bastante pasando como argumento la capacidad del array y retornando un valor útil (el numero de caracteres almacenados).

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

x64core

@yoel_alejandro:
En proyectos simples es facil, pero en proyectos considerablemente grandes no podes esperar que has manipulado todos los posibles errores.
Pero con ese comentario luces de las personas que tiene poca experiencia en eso. de todos modos espero verte
más seguido por aquí ya vamos a ver si realmente sabes como dices.

Yoel Alejandro

#9
A ver, rir3760, explícame bien lo del problema con el avance de línea, porque según recuerdo una vez tuve problemas con eso. De ahí que siempre le pongo dicha prevención en el código. Tengo entendido además que por lo general los sistemas Windows codifican el fin de línea con dos caracteres (0x0D + 0x0A, o en decimal 13 + 10), mientras los UNIX usan simplemente el 0x0A.

Vamos por partes: Me tratas de decir que si por ejemplo fgetc() encuentra una sucesión CR + LF , entonces lee un sólo '\n' y avanza el indicador de posición de archivo en dos bytes, de modo que la próxima llamada a fgect() leerá el siguiente carácter después del LF. ¿¿Es eso así??

==============
Ah, y x64Core, no ingresé a este foro para pelear con nadie, sino para ayudar a los demás. Simplemente tengo una filosofía de que me gusta hacer las cosas a mano, en lugar de recurrir a recursos o softwares sofisticados.

Por ejemplo, uso la IDE más sencilla y sólo como editor, pues rara vez compilo usando los íconos de la barra de herramientas. Yo compilo desde la terminal y le meto yo mismo las directivas de "include" y de "lib" en la orden de compilación.

Hasta ahora me ha funcionado y he creado aplicaciones de cierta complejidad. Por supuesto me lleva más tiempo pero estoy seguro de que he construido y verificado POR MÍ MISMO cada línea de mi código, por eso confío en él.

De nuevo, no es por polemizar, simplemente es mi filosofía de trabajo. Cada quién tiene la suya y se le respeta  :)
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)