将原始结构内容(字节)写入C中的文件。对写入的实际大小感到困惑
基本问题,但我希望这个结构占用13个字节的空间(1个用于char,12个用于3个无符号的int)。 相反, sizeof(ESPR_REL_HEADER)
给了我16个字节。
typedef struct { unsigned char version; unsigned int root_node_num; unsigned int node_size; unsigned int node_count; } ESPR_REL_HEADER;
我想要做的是用一些值初始化这个结构并将它包含的数据(原始字节)写入文件的开头,这样当我打开这个文件后我才能重构这个结构并获得一些元数据有关文件其余部分包含内容的数据。
我正在初始化结构并将其写入文件,如下所示:
int esprime_write_btree_header(FILE * fp, unsigned int node_size) { ESPR_REL_HEADER header = { .version = 1, .root_node_num = 0, .node_size = node_size, .node_count = 1 }; return fwrite(&header, sizeof(ESPR_REL_HEADER), 1, fp); }
我试验时node_size
当前是4。
在向其编写结构后,该文件包含以下数据:
-bash$ hexdump test.dat 0000000 01 bf f9 8b 00 00 00 00 04 00 00 00 01 00 00 00 0000010
我希望它实际上包含:
-bash$ hexdump test.dat 0000000 01 00 00 00 00 04 00 00 00 01 00 00 00 0000010
请原谅新生。 我正在努力学习:)如何有效地将我的struct的数据组件写入文件?
微处理器不是为从任意地址获取数据而设计的。 诸如4字节int
s之类的对象应该只存储在可被4整除的地址中。 此要求称为对齐 。
C使编译器可以自由地在struct成员之间插入填充字节以对齐它们。 填充量只是不同平台之间的一个变量,另一个主要变量是字节序 。 这就是为什么如果您希望程序在多台计算机上运行,您不应该简单地将结构“转储”到磁盘上。
最佳实践是显式编写每个成员,并在二进制输出之前使用htonl
将endianness修复为big-endian。 回读时,使用memcpy
移动原始字节,不要使用
char *buffer_ptr; ... ++ buffer_ptr; struct.member = * (int *) buffer_ptr; /* potential alignment error */
而是做
memcpy( buffer_ptr, (char *) & struct.member, sizeof struct.member ); struct.member = ntohl( struct.member ); /* if member is 4 bytes */
这是因为结构填充,请参阅http://en.wikipedia.org/wiki/Sizeof#Implementation
当您使用fwrite
按原样编写结构时,您将在内存中进行编写,包括由于填充而插入的结构内部的“死字节”。 此外,您的多字节数据是使用系统的endiannes编写的。
如果您不希望发生这种情况,请编写一个序列化结构中数据的函数。 您只能编写非填充区域,还可以按可预测的顺序写入多字节数据(例如,以网络字节顺序 )。
该结构受制于对齐规则,这意味着其中的一些项目被填充。 看一下,看起来第一个unsigned char
字段被填充到4个字节。
这里的一个问题是规则可能因系统而异,所以如果你在一个平台上用一个编译器编译的程序中使用fwrite
整体编写结构,然后尝试使用fread
在另一个平台上读取它,你可能会得到垃圾,因为第二个程序会假设数据是对齐的,以适应它的结构布局的概念。
通常,您必须:
-
确定保存的数据文件仅对共享某些特征的程序构建有效(取决于您使用的编译器的记录行为),或者
-
不要将整个结构编写为一个,而是实现更正式的数据格式,其中每个元素都是单独编写的,其大小是明确控制的。
(相关问题是字节顺序可能不同;同样的选择通常也适用于此,除了在选项2中您要明确指定数据格式的字节顺序。)
努力不要这样做! 大小差异是由编译器/链接器使用的填充和对齐引起的,以便通过速度优化对变量的访问。 填充和对齐规则与语言和操作系统。 此外,由于字节顺序,在不同硬件上写入和读取它们可能会有问题。
在不能被误解的结构中逐字节写入元数据。 以空值终止的ASCII字符串即可。
我使用了由Troy D. Hanson编写的一个很棒的开源代码片段,名为TPL: http : //tpl.sourceforge.net/ 。 使用TPL,您没有任何外部依赖。 这就像将tpl.c和tpl.h包含到您自己的程序中并使用TPL API一样简单。
以下是指南: http : //tpl.sourceforge.net/userguide.html
这是因为称为内存对齐的东西。 第一个char扩展为占用4个字节的内存。 实际上,像int
这样的较大类型只能在4字节块的开头“开始”,因此编译器会填充字节以达到这一点。
我对位图标题有同样的问题,从2个char开始。 我在结构体中使用了一个char bm[2]
并想知道2天里#$%^标题的第3和第4个字节在哪里…
如果要防止这种情况,可以使用__attribute__((packed))
但要注意,内存对齐是程序运行方便所必需的 。
如果要以特定格式写入数据,请使用unsigned char
数组…
unsigned char outputdata[13]; outputdata[0] = 1; outputdata[1] = 0; /* ... of course, use data from struct ... */ outputdata[12] = 0; fwrite(outputdata, sizeof outputdata, 1, fp);