C: error con la lectura () de un archivo, almacenamiento en una matriz e impresión de salida correctamente

Soy nuevo en C, por lo que no estoy exactamente seguro de dónde está mi error. Sin embargo, sé que la gran parte del problema radica en cómo almaceno los dobles en la matriz d_buffer (doble) o en la forma en que los imprimo.

Específicamente, mi salida sigue imprimiendo números extremadamente grandes (con alrededor de 10-12 dígitos antes del punto decimal y un rastro de ceros después). Además, esta es una adaptación de un programa anterior para permitir entradas dobles, por lo que realmente solo agregué el dos sentencias if (en el bucle for "read" y el bucle for "printf") y la declaración d_buffer.

Agradecería cualquier aporte ya que he pasado varias horas con este error.

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>


struct DataDescription
{
   char fieldname[30];
   char fieldtype;
   int  fieldsize;
};




/* -----------------------------------------------
   eof(fd): returns 1 if file `fd' is out of data
   ----------------------------------------------- */
int eof(int fd)
{
   char c;

   if ( read(fd, &c, 1) != 1 )
      return(1);
   else
    { lseek(fd, -1, SEEK_CUR);
      return(0);
    }
}


void main()
{
   FILE *fp;    /* Used to access meta data */
   int fd;  /* Used to access user data */

   /* ----------------------------------------------------------------
      Variables to hold the description of the data  - max 10 fields
      ---------------------------------------------------------------- */
   struct DataDescription DataDes[10];  /* Holds data descriptions
                                           for upto 10 fields */
   int  n_fields;                       /* Actual # fields */

   /* ------------------------------------------------------
      Variables to hold the data  - max 10 fields....
      ------------------------------------------------------ */
   char c_buffer[10][100];  /* For character data */
   int  i_buffer[10];       /* For integer data */
   double d_buffer[10];

   int i, j;
   int found;

   printf("Program for searching a mini database:\n");

   /* =============================
      Read in meta information
      ============================= */
   fp = fopen("db-description", "r");
   n_fields = 0;
   while ( fscanf(fp, "%s %c %d", DataDes[n_fields].fieldname, 
          &DataDes[n_fields].fieldtype,
          &DataDes[n_fields].fieldsize) > 0 )
      n_fields++;

   /* ---
      Prints meta information
      --- */
   printf("\nThe database consists of these fields:\n");
   for (i = 0; i < n_fields; i++)
      printf("Index %d: Fieldname `%s',\ttype = %c,\tsize = %d\n", 
        i, DataDes[i].fieldname, DataDes[i].fieldtype,
        DataDes[i].fieldsize);
   printf("\n\n");

   /* ---
      Open database file
      --- */
   fd = open("db-data", O_RDONLY);

   /* ---
      Print content of the database file
      --- */
   printf("\nThe database content is:\n");
   while ( ! eof(fd) )
   {  /* ------------------
         Read next record
         ------------------ */
      for (j = 0; j < n_fields; j++)
      {  
     if ( DataDes[j].fieldtype == 'I' )
        read(fd, &i_buffer[j], DataDes[j].fieldsize);
     if ( DataDes[j].fieldtype == 'F' )
        read(fd, &d_buffer[j], DataDes[j].fieldsize);
     if ( DataDes[j].fieldtype == 'C' )
        read(fd, &c_buffer[j], DataDes[j].fieldsize);       
      }

      double d;
      /* ------------------
         Print it...
         ------------------ */
      for (j = 0; j < n_fields; j++)
      {   
     if ( DataDes[j].fieldtype == 'I' )
        printf("%d ", i_buffer[j]);
     if ( DataDes[j].fieldtype == 'F' )
        d = d_buffer[j];
        printf("%lf ", d);
     if ( DataDes[j].fieldtype == 'C' )
        printf("%s ", c_buffer[j]);
      }
      printf("\n");
   }
   printf("\n");
   printf("\n");


}

Salida esperada: 3 filas de datos que terminan con el número "e = 2.18281828"

Para reproducir el problema, los dos archivos siguientes deben estar en el mismo directorio que el archivo lookup-data.c:
- [datos-bd][1]
- [db-descripción][2]

preguntado el 04 de julio de 12 a las 02:07

¿Cómo se llena la base de datos y el índice para reproducir el problema? -

No podemos reproducir su problema porque no tenemos los archivos de entrada. Si no puede compartir esos archivos, ¿podría compartir el resultado que obtiene? me interesa saber que fieldsize es para los valores flotantes. -

Infórmese sobre la instrucción switch y utilícela. Y no está comprobando el valor de retorno de read... hacer eso sería mucho mejor que su funky función eof. -

Tu nuevo printf() para el valor flotante está usando código de formato "%d"; debería ser "%f". -

Voy a tratar de recordar esto para siempre: un big-endian 1 leer en una máquina little-endian se imprime como 16777216. Eso es 2 elevado a la potencia 24. También funciona al revés: un little-endian 1 leído en una máquina big-endian también se imprimirá como 16777216. Si hubiera memorizado eso, ¡podría haberlo descubierto mucho más rápido! -

3 Respuestas

EDITAR: Mis especulaciones anteriores estaban todas equivocadas. El problema era que el archivo de la base de datos tenía números big-endian y los datos se leían en una computadora little-endian. Pase a la sección marcada "COMENZAR A LEER AQUÍ" para evitar perder el tiempo con las primeras especulaciones (dejadas aquí por su valor histórico muy limitado).

Sospecho que tu problema está relacionado con el printf() especificación de formato %lf utilizado para imprimir valores, junto con el hecho de que declaró d_buffer ser de tipo int. Probable sizeof(double) > sizeof(int) es verdadero, por lo que está interpretando bytes adicionales de datos como parte de los valores flotantes.

No estoy seguro de esto ya que no puedo ver los datos que su programa está usando para ejecutarse, pero si su fieldsize para datos flotantes es sizeof(float) más bien que sizeof(double) entonces tal vez esté almacenando los valores flotantes en d_buffer muy bien, pero estropeándolos al imprimir. Alternativamente, si fieldsize es igual a sizeof(double) y sizeof(double) es mayor que sizeof(int), entonces estás descartando el final de d_buffer y luego algo está corrompiendo sus datos.

Le sugiero que cambie la declaración a double d_buffer[10]; y ver si su programa funciona mejor. Además, mira si fieldsize se establece a sizeof(float) or sizeof(double); si esto es sizeof(float) luego declara d_buffer como tipo float y usa este código:

  if ( DataDes[j].fieldtype == 'F' )
  {
    double d = d_buffer[j];
    printf("%lf ", d);
  }

EDITAR: Además, te recomiendo que cambies a usar fopen() y fread() para todas sus E/S. Lo básico open() y read() puede regresar EINTR lo que significa que debe volver a intentar la operación, por lo que estos solo se usan correctamente dentro de un ciclo que volverá a intentar la llamada si regresa EINTR. La fopen() y fread() las llamadas lo aíslan de ese tipo de detalles y tienen algo de almacenamiento en búfer que puede hacer que sus programas funcionen sin problemas. (Estoy bastante seguro de que su proyecto actual solo lee y escribe pequeñas cantidades de datos, por lo que la diferencia de rendimiento probablemente no le importe mucho en este momento).

Además, le sugiero que se deshaga de su eof() función; es muy inusual y probablemente un poco lento leer un carácter y luego usar fseek() para volver a ponerlo. La biblioteca C tiene la función fgetc() que obtiene un carácter del flujo de entrada, y puede probar ese carácter para ver si es el EOF valor para detectar el final del archivo. Y la biblioteca C también tiene la función ungetc() que puede hacer retroceder un carácter; esto no implicará realmente buscar el disco, sino que simplemente volverá a colocar el carácter en algún búfer en algún lugar. Pero ni siquiera necesitas usar fgetc() or ungetc() por su código; solo verifique el valor de retorno de fread() y si obtiene una lectura de longitud cero, sabrá que llegó al final del archivo. En el código de producción, deberá verificar el valor de retorno de cada llamada de función de todos modos; no puede salirse con la esperanza de que cada lectura tenga éxito.

EDITAR: una cosa más que podría intentar: cambiar el código de formato de "%lf" para sólo "%f" y mira lo que pasa. No estoy exactamente seguro de qué es eso l servirá y no deberías necesitarlo. simple viejo "%f" debe formatear un double. Pero puede que no cambie nada: según esta página web que encontré, en printf() no hay diferencia entre "%lf" y "%f".

http://www.dgp.toronto.edu/~ajr/209/notes/printf.html

* EMPIEZA A LEER AQUI *

EDITAR: Bien, una cosa que he descubierto con seguridad. El formato de la base de datos es un valor de índice (un valor entero), luego un valor flotante y luego un valor de cadena. Deberá leer cada uno para avanzar a la posición actual dentro del archivo. Entonces, ¿su código actual que verifica los códigos de formato y decide qué leer? Incorrecto; necesita leer un número entero, un flotante y una cadena para cada registro.

EDITAR: Bien, aquí hay un programa de Python correcto que puede leer la base de datos. No se molesta en leer el archivo de metadatos; Acabo de codificar las constantes (está bien, ya que es solo un programa desechable).

import struct

_format_rec = ">id20s"  # big-endian: int, double, 20-char string
_cb_rec = struct.calcsize(_format_rec) # count of bytes in this format

def read_records(fname):
    with open(fname) as in_f:
        try:
            while True:
                idx, f, s = struct.unpack(_format_rec, in_f.read(_cb_rec))
                # Python doesn't chop at NUL byte by default so do it now.
                s, _, _ = s.partition('\0')
                yield (idx, f, s)
        except struct.error:
            pass

if __name__ == "__main__":
    for i, (idx, f, s) in enumerate(read_records("db-data")):
        print "%d) index: %d\tfloat: %f \ttext: \"%s\"" % (i, idx, f, s)

Entonces, los valores del índice son enteros de 32 bits, big-endian; los flotantes son flotantes de 64 bits, big-endian; y los campos de texto tienen 20 caracteres fijos (es decir, una cadena de 0 a 19 caracteres más un byte NUL de terminación).

Aquí está la salida del programa anterior:

0) index: 1     float: 3.141593         text: "Pi"
1) index: 2     float: 12.345000        text: "Secret Key"
2) index: 3     float: 2.718282         text: "The number E"

Ahora, cuando trato de compilar su código C, obtengo valores basura porque mi computadora es little-endian. ¿Está tratando de ejecutar su código C en una computadora little-endian?

EDITAR: para responder al comentario más reciente, que es una pregunta: para cada registro de entrada, debe llamar read() tres veces. La primera vez que lee el índice, que es un número entero de 4 bytes (big-endian). La segunda lectura es un valor flotante de 8 bytes, también big-endian. La tercera lectura es de 20 bytes como una cadena. Cada lectura mueve la posición actual en el archivo; las tres lecturas juntas leen un solo registro del archivo. Una vez que haya leído e impreso tres registros, habrá terminado.

Dado que mi computadora es little-endian, es difícil obtener los valores correctamente en C, pero lo hice. Hice una unión que me permite leer un valor de 8 bytes como un número entero o flotante, y lo usé como búfer para la llamada a read(); entonces llamé __builtin_bswap64() (una función en GCC) para cambiar el valor big-endian a little-endian, almacenó el resultado como un número entero de 64 bits y lo leyó como un flotante. también usé __builtin_bswap32() para intercambiar el índice entero. Mi programa C ahora imprime:

The database content is:
1 3.141593 Pi
2 12.345000 Secret Key
3 2.718282 The number E

Por lo tanto, lea cada registro, asegúrese de estar en lo correcto con respecto al endian-ness de los datos, y tendrá un programa que funcione.

EDITAR: Aquí hay fragmentos de código que muestran cómo solucioné el problema de endianness.

typedef union
{
    unsigned char buf[8];
    double d;
    int64_t i64;
    int32_t i32;
} U;

// then, inside of main():

   printf("\nThe database content is:\n");
   {  /* ------------------
         Read next record
         ------------------ */
      for (j = 0; j < n_fields; j++)
      {
        U u;
        read(fd, u.buf, 4);
        u.i32 = __builtin_bswap32(u.i32);
        i_buffer[j] = u.i32;
        read(fd, u.buf, 8);
        u.i64 = __builtin_bswap64(u.i64);
        d_buffer[j] = u.d;
        read(fd, c_buffer[j], 20);
      }

Estoy un poco sorprendido de que la base de datos estuviera en formato big-endian. Cualquier computadora que use un procesador de la familia x86 es little-endian.

Definitivamente no debería codificar esos números (4, 8, 20) como lo hice yo; debe usar los metadatos que recibió. Te dejo eso.

EDITAR: Tampoco deberías llamar __builtin_bswap32() or __builtin_bswap64(). Deberías llamar ntohl() y... no estoy seguro de cuál es el de 64 bits. Pero ntohl() es portátil; omitirá el intercambio si está compilando en una computadora big-endian, y hará el intercambio si está compilando en una computadora little-endian.

EDITAR: Encontré una solución para un equivalente de 64 bits a ntohl(), aquí mismo en StackOverflow.

https://stackoverflow.com/a/4410728/166949

Es simple si solo te importa Linux: en lugar de usar #include <arpa/inet.h> puedes usar #include <endian.h> Y luego usar be32toh() y be64toh().

Una vez que tenga esto, puede leer el archivo de la base de datos así:

u.i64 = be64toh(u.i64);

Si compila el código anterior en una máquina big-endian, se compilará en un no hacer nada y leerá el valor big-endian. Si compila el código anterior en una máquina little-endian, se compilará al equivalente de __builtin_bswap64() e intercambie los bytes para que el valor de 64 bits se lea correctamente.

EDITAR: dije en un par de lugares que necesita hacer tres lecturas separadas: una para obtener el índice, otra para obtener el flotante y otra para obtener la cadena. En realidad, puede declarar un struct y emita una sola lectura que extraiga todos los datos en una sola lectura. Sin embargo, la parte complicada es que el compilador de C podría insertar bytes de "relleno" dentro de su struct que causaría la estructura de bytes del struct para no coincidir exactamente con la estructura de bytes del registro leído del archivo. El compilador de C debe proporcionar una forma de controlar los bytes de alineación (un #pragma declaración) pero no quería entrar en detalles. Para este programa simple, simplemente leer tres veces resuelve el problema.

contestado el 23 de mayo de 17 a las 13:05

steveha, realicé los cambios sugeridos pero todavía obtuve un resultado no deseado. Lo he puesto en una edición anterior. - ns1

¿Puedes compartir el archivo db-description? - yuklai

"No estoy exactamente seguro de lo que haré". Bueno, podrías leer el estándar y averiguarlo. - jim balter

@JimBalter, también sospecho de características como esa l y si la implementación los hizo bien o no. Usualmente uso compiladores semi-rotos (Microsoft y varios procesadores DSP integrados) y no asumo que siempre se sigue el estándar para las características menos comunes. En lugar de leer el estándar para descifrarlos, tiendo a no usarlos, a menos que crea que los necesito. Pero claro, leer la documentación siempre es algo bueno. - steveha

@JimBalter, me disculpo por ofenderte. Que tengas una buena vida. - steveha

¿Por qué supone que los tipos de campo que está leyendo de db-description coinciden con los datos en db-data? Seguro que no lo haría. Como mínimo, debe imprimir esos tipos de campo y confirmar que son lo que espera que sean.

Respondido 04 Jul 12, 03:07

Solo estoy a cargo de editar el archivo c, y los tipos de campo que se leen desde db-description coinciden. Pero no mencioné eso arriba, así que es un buen punto. - ns1

Para obtener los resultados que desea, sugiero reemplazar

int  d_buffer[10];

con:

double d_buffer[10];

Respondido 14 Jul 12, 16:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.