C - 文件的读取()错误,存储在数组中并正确打印输出

Posted

技术标签:

【中文标题】C - 文件的读取()错误,存储在数组中并正确打印输出【英文标题】:C - Error with read() of a file, storage in an array, and printing output properly 【发布时间】:2012-07-04 01:25:07 【问题描述】:

我是 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 文件位于同一目录中: - [数据库数据][1] - [数据库描述][2]

【问题讨论】:

如何填充数据库和索引以重现问题? 我们无法重现您的问题,因为我们没有输入文件。如果你不能分享这些文件,你能分享你得到的输出吗?我很想知道 fieldsize 的浮点值是什么。 请了解switch语句,并使用它。而且你没有检查 read ... 的返回值,这样做会比你时髦的 eof 函数好得多。 您的浮点值新printf() 使用格式代码"%d";应该是"%f" 我将尝试永远记住这一点:在小端机器上读取的大端 1 打印为 16777216。这是 2 的 24 次方。它也以另一种方式工作:在大端机器上读取的小端 1 也将打印为 16777216。如果我记住了这一点,我可以更快地弄清楚这一点! 【参考方案1】:

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

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

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

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

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

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

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

编辑:您还可以尝试一件事:将格式代码从 "%lf" 更改为 "%f",看看会发生什么。我不确定l 会做什么,你不应该需要它。普通的旧 "%f" 应该格式化为 double。但它可能不会改变任何东西:根据我发现的这个网页,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() 三次。第一次读取索引时,它是一个 4 字节整数(big-endian)。第二次读取的是一个 8 字节的浮点值,也是大端的。第三次读取是 20 个字节的字符串。每次读取都会移动文件中的当前位置;三个读取一起从文件中读取单个记录。阅读并打印三份记录后,您就完成了。

由于我的计算机是 little-endian,因此在 C 中正确获取值很棘手,但我做到了。我做了一个联合,让我可以将一个 8 字节的值读出为整数或浮点数,并将其用作调用 read() 的缓冲区;然后我调用__builtin_bswap64()(GCC 中的一个功能)将大端值交换为小端,将结果存储为 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()__builtin_bswap64()。您应该致电ntohl() 并且...我不确定 64 位是什么。但是ntohl() 是可移植的;如果您在大端计算机上编译,它将跳过交换,如果您在小端计算机上编译,它会进行交换。

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

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

如果您只关心 Linux,这很简单:您可以使用#include &lt;endian.h&gt;,然后使用be32toh()be64toh(),而不是使用#include &lt;arpa/inet.h&gt;

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

u.i64 = be64toh(u.i64);

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

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

【讨论】:

steveha,我做了你建议的更改,但我仍然得到了意外的输出。我已经把它放在上面的编辑中。 你真的可以分享文件 db-description 吗? “我不确定我会做什么”——好吧,你可以阅读标准并找出答案。 @JimBalter,我也怀疑l 之类的功能以及实现是否正确。我经常使用半损坏的编译器(Microsoft 和各种嵌入式 DSP 处理器),并且我不认为对于不太常见的功能总是遵循该标准。除非我认为我需要它们,否则我倾向于不使用它们,而不是阅读标准来弄清楚它们。但可以肯定的是,阅读文档总是一件好事。 @JimBalter,我很抱歉冒犯了你。祝你生活愉快。【参考方案2】:

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

【讨论】:

我只负责编辑c文件,从db-description读入的字段类型确实匹配。但我没有在上面提到这一点,所以这是一个好点。【参考方案3】:

为了得到你想要的结果,我建议替换

int  d_buffer[10];

与:

double d_buffer[10];

【讨论】:

以上是关于C - 文件的读取()错误,存储在数组中并正确打印输出的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在我的结构中打印名称?

C 语言文件操作 ( 将结构体写出到文件中并读取结构体数据 | 将结构体数组写出到文件中并读取结构体数组数据 )

将字符串存储在 mmap 共享数组中? (C)

C 编程:读取文件并存储在结构数组中

从文本文件中读取单词并存储到 C 中的动态数组 Valgrind 错误中

C 语言文件操作 ( 学生管理系统 | 命令行接收数据填充结构体 | 结构体写出到文件中 | 查询文件中的结构体数据 )