将原始结构内容(字节)写入C中的文件。混淆写入的实际大小

时间:2012-04-14 11:16:50

标签: c struct sizeof fwrite

基本问题,但我希望这个结构占用13个字节的空间(1个用于char,12个用于3个无符号整数)。相反,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的数据组件写入文件?

8 个答案:

答案 0 :(得分:6)

微处理器不是为从任意地址获取数据而设计的。诸如4字节int s之类的对象应仅存储在可被4整除的地址中。此要求称为alignment

C使编译器可以自由地在struct成员之间插入padding bytes以对齐它们。填充量只是不同平台之间的一个变量,另一个主要变量是endianness。这就是为什么如果你想让程序在多台机器上运行,你不应该简单地将结构“转储”到磁盘上。

最佳做法是明确地编写每个成员,并在二进制输出之前使用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 */

答案 1 :(得分:3)

这是因为结构填充,请参阅http://en.wikipedia.org/wiki/Sizeof#Implementation

答案 2 :(得分:1)

当您使用fwrite编写结构时,您将在内存中进行编写,包括由于 padding 而插入的结构内部的“死字节”。此外,您的多字节数据是使用系统的 endiannes 编写的。

如果您不希望发生这种情况,请编写一个序列化结构中数据的函数。您只能编写非填充区域,并以可预测的顺序写入多字节数据(例如,在network byte order中)。

答案 3 :(得分:1)

结构符​​合对齐规则,这意味着它中的一些项目被填充。看一下,看起来第一个unsigned char字段已填充到4个字节。

这里的一个问题是规则可能因系统而异,所以如果在一个平台上用一个编译器编译的程序中使用fwrite编写整个结构,然后尝试在另一个上使用fread读取它,你可能会得到垃圾,因为第二个程序会假设数据是对齐的,以适应它的结构布局的概念。

通常,您必须:

  1. 确定保存的数据文件仅对共享某些特征的程序版本有效(取决于您使用的编译器的记录行为),或

  2. 不要将整个结构整体编写为一个,而是实现更正式的数据格式,其中每个元素都是单独编写的,其大小是明确控制的。

  3. (相关问题是字节顺序可能不同;同样的选择通常也适用于此,除了在选项2中您要明确指定数据格式的字节顺序。)

答案 4 :(得分:1)

这是因为称为内存对齐的东西。第一个char扩展为占用4个字节的内存。事实上,像int这样的较大类型只能在4个字节的块的开头“开始”,因此编译器会填充字节以达到这一点。

我遇到了与位图标题相同的问题,从2个char开始。我在结构体中使用了char bm[2]并想知道2天里#$%^标题的第3个和第4个字节在哪里...

如果您想阻止这种情况,可以使用__attribute__((packed))beware, memory alignment IS necessary to your program to run conveniently

答案 5 :(得分:1)

尽量不要这样做!大小差异是由编译器/链接器使用的填充和对齐引起的,以便通过速度优化对变量的访问。填充和对齐规则与语言和操作系统。此外,由于字节顺序,在不同的硬件上写入和读取它们可能会有问题。

将字符串逐字节写入一个不会被误解的结构中。以空值终止的ASCII字符串正常。

答案 6 :(得分:1)

我使用了一个由Troy D. Hanson编写的令人敬畏的开源代码段,称为TPL:http://tpl.sourceforge.net/。 使用TPL,您没有任何外部依赖。这就像将tpl.c和tpl.h包含到您自己的程序中并使用TPL API一样简单。

以下是指南:http://tpl.sourceforge.net/userguide.html

答案 7 :(得分:0)

如果您想以特定格式编写数据,请使用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);