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) > sizeof(int)
可能为真,因此您将额外的数据字节解释为浮点值的一部分。
我不确定这一点,因为我看不到您的程序用于运行的数据,但如果您的浮动数据的fieldsize
是sizeof(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 <endian.h>
,然后使用be32toh()
和be64toh()
,而不是使用#include <arpa/inet.h>
。
一旦你有了这个,你可以像这样读取数据库文件:
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 语言文件操作 ( 将结构体写出到文件中并读取结构体数据 | 将结构体数组写出到文件中并读取结构体数组数据 )
从文本文件中读取单词并存储到 C 中的动态数组 Valgrind 错误中
C 语言文件操作 ( 学生管理系统 | 命令行接收数据填充结构体 | 结构体写出到文件中 | 查询文件中的结构体数据 )