3

我是 C 新手,所以我不确定我的错误在哪里。但是,我确实知道问题的很大一部分在于我如何将双打存储在 d_buffer (double) 数组中,或者我打印它的方式。

具体来说,我的输出一直在打印非常大的数字(小数点前大约有 10-12 位数字,小数点后有一行零。此外,这是对旧程序的改编,允许双输入,所以我只添加了两个 if 语句(在“read”for 循环和“printf”for 循环中)和 d_buffer 声明。

我将不胜感激任何输入,因为我在这个错误上花了几个小时。

#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");


}

预期输出:以数字“e = 2.18281828”结尾的 3 行数据

要重现该问题,以下两个文件需要与 lookup-data.c 文件位于同一目录中:
- [db-data][1]
- [db-description][2]

4

3 回答 3

1

编辑:我之前的猜测都是错误的。问题是数据库文件有 big-endian 数字,而数据是在 little-endian 计算机上读取的。跳到标有“从这里开始阅读”的部分,以避免在早期的推测上浪费你的时间(因为它们的历史价值非常有限而留在这里)。

我怀疑您的问题与用于打印值的printf()格式规范以及您声明为 type%lf的事实有关。可能是真的,因此您将额外的数据字节解释为浮点值的一部分。d_bufferintsizeof(double) > sizeof(int)

我不确定这一点,因为我看不到您的程序用来运行的数据,但是如果您的fieldsizefor float 数据sizeof(float)不是sizeof(double)那么可能您将浮点值存储d_buffer得很好,但在打印时会弄乱它们。或者,如果fieldsize等于sizeof(double)并且sizeof(double)大于sizeof(int),那么您正在注销末尾,d_buffer然后某些东西正在破坏您的数据。

我建议您将声明更改为double d_buffer[10];,看看您的程序是否运行得更好。另外,看看是否fieldsize设置为sizeof(float)or sizeof(double); 如果它被sizeof(float)声明d_buffer为类型float并使用此代码:

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

编辑:另外,我建议您切换到使用fopen()andfread()用于所有 I/O。基本的open()并且read()可以返回EINTR,这意味着您需要重试操作,因此这些仅在循环中正确使用,如果返回,将重试调用EINTRfopen()和调用使您免受这种fread()细节的影响,并有一些缓冲可以使您的程序运行得更顺畅。(我很确定您当前的项目只是读取和写入少量数据,因此此时性能差异对您来说可能并不重要。)

另外,我建议你摆脱你的eof()功能;读取一个字符然后用fseek()它放回它是非常不寻常的并且可能有点慢。C 库具有fgetc()从输入流中获取一个字符的功能,您可以测试该字符以查看它是否是EOF检测文件结尾的值。而且C库也有ungetc()回退一个字符的功能;这不会涉及实际寻找磁盘,而只是将字符放回某个缓冲区中的某个地方。但是您甚至不需要使用fgetc()orungetc()为您的代码;只需检查返回值fread()如果你得到一个零长度的读取,你就知道你遇到了文件结尾。在生产代码中,无论如何您都需要检查每个函数调用的返回值;你不能只希望每次阅读都能成功。

编辑:您可以尝试的另一件事:将格式代码从更改"%lf"为 just "%f",看看会发生什么。我不确定那l会做什么,你不应该需要它。普通旧的"%f"应该格式化一个double. 但它可能不会改变任何东西:根据我发现的这个网页, in和printf()之间没有区别。"%lf""%f"

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

* 从这里开始阅读 *

编辑:好的,我已经确定了一件事。数据库格式是一个索引值(一个整数值),然后是一个浮点值,然后是一个字符串值。您将需要阅读每一个以推进文件中的当前位置。那么您当前的代码是检查格式代码并决定要读取哪些内容?不正确; 您需要为每条记录读取一个整数、一个浮点数和一个字符串。

编辑:好的,这是一个可以读取数据库的正确 Python 程序。无需费心读取元数据文件;我只是在常量中硬编码(没关系,因为它只是一个一次性程序)。

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)

所以索引值是 32 位整数,大端;浮点数是 64 位浮点数,大端;并且文本字段是固定的 20 个字符(因此是 0 到 19 个字符加上一个终止 NUL 字节的字符串)。

这是上述程序的输出:

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"

现在,当我尝试编译你的 C 代码时,我得到了垃圾值,因为我的计算机是 little-endian。您是否尝试在 little-endian 计算机上运行您的 C 代码?

编辑:要回答最近的评论,这是一个问题:对于每个输入记录,您必须调用read()3 次。第一次读取索引时,它是一个 4 字节整数(big-endian)。第二次读取的是一个 8 字节的浮点值,也是大端的。第三次读取是 20 个字节的字符串。每次读取都会移动文件中的当前位置;三个读取一起从文件中读取单个记录。在您阅读并打印了三个记录之后,您就完成了。

由于我的计算机是 little-endian,因此在 C 中正确获取值很棘手,但我做到了。我做了一个联合,让我可以将一个 8 字节的值作为整数或浮点数读出,我用它作为调用的缓冲区read();然后我调用__builtin_bswap64()(GCC 中的一个功能)将 big-endian 值交换为 little-endian,将结果存储为 64 位整数,并将其作为浮点数读出。我也用来__builtin_bswap32()交换整数索引。我的 C 程序现在打印:

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

因此,请阅读每条记录,确保数据的字节序正确,并且您将拥有一个工作程序。

编辑:这里是显示我如何解决字节顺序问题的代码片段。

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);
      }

我有点惊讶数据库是大端格式。任何使用 x86 系列处理器的计算机都是 little-endian。

你绝对不应该像我一样对这些数字(4、8、20)进行硬编码;您应该使用收到的元数据。我会把它留给你。

编辑:你也不应该打电话__builtin_bswap32()or __builtin_bswap64()。你应该打电话给ntohl()...我不确定 64 位是什么。但ntohl()便携;如果你在大端计算机上编译,它将跳过交换,如果你在小端计算机上编译,它会进行交换。

ntohl()编辑:我在 StackOverflow 上找到了等效于 64 位的解决方案。

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

如果您只关心 Linux,这很简单:#include <arpa/inet.h>您可以使用#include <endian.h>然后使用be32toh()and来代替使用be64toh()

一旦你有了这个,你可以像这样读取数据库文件:

u.i64 = be64toh(u.i64);

如果您在大端机器上编译上述代码,它将编译为无操作并读取大端值。如果您在 little-endian 机器上编译上述代码,它将编译为等效__builtin_bswap64()的字节并交换字节,以便正确读取 64 位值。

编辑:我在几个地方说过你需要做三个单独的读取:一个获取索引,一个获取浮点数,一个获取字符串。实际上,您可以声明 astruct并发出一次读取,该读取将在一次读取中提取所有数据。但是,棘手的部分是 C 编译器可能会在您的内部插入“填充”字节struct,这会导致 的字节结构与struct从文件中读取的记录的字节结构不完全匹配。C 编译器应该提供一种方法来控制对齐字节(一条#pragma语句),但我不想深入了解细节。对于这个简单的程序,只需阅读三遍即可解决问题。

于 2012-07-04T01:33:06.877 回答
0

为了获得你想要的结果,我建议更换

int  d_buffer[10];

和:

double d_buffer[10];
于 2012-07-04T01:39:26.250 回答
0

为什么假设您从 db-description 读取的字段类型与 db-data 中的数据匹配?我肯定不会。至少您应该打印出这些字段类型并确认它们是您所期望的。

于 2012-07-04T02:26:36.593 回答