今天,我非常惊讶地发现了
当sizeof运算符应用于类,结构或联合类型时,结果是该类型的对象中的字节数,以及为对齐单词边界上的成员而添加的任何填充。结果不一定与通过添加各个成员的存储要求计算的大小相对应。
我不知道它,并且我很确定这件事打破了我的一些旧代码:读取我以前使用的结构的二进制文件:
struct Header
{
union {
char identc[4];
uint32 ident;
};
uint16 version;
};
并直接使用fread
驱动的sizeof
读取这6个字节:
fread( &header, sizeof(header), 1, f );
但现在sizeof(header)
会返回8
!
对于较旧的GCC版本sizeof(header)
是否可能返回6
,或者我的想法完全消失了?
无论如何还有其他运算符(或预处理程序指令或其他)让编译器知道结构有多大 - 不包括填充?
否则从一个不需要编写太多代码的文件中读取原始数据结构的干净方法是什么?
修改: 我知道这不是读取/写入二进制数据的正确方法:根据机器的endianess和东西,我会得到不同的结果。无论如何这种方法是最快的方法,我是juist试图读取一些二进制数据以快速获取其内容,而不是编写一个我将在未来使用或发布的好应用程序。
答案 0 :(得分:9)
你想要的是#pragma pack命令。这允许您将包装设置为您想要的任何数量。通常,您会在结构定义之前将打包值设置为1(或者是0?),然后在定义之后将其返回到默认值。
请注意,这并不能保证系统之间的可移植性。
另见:use-of-pragma-in-c以及关于SO
的各种其他问题答案 1 :(得分:3)
是的,您提供的代码不可移植。不仅结构大小而且字节顺序可能不同。
答案 2 :(得分:2)
这不是处理二进制文件的正确方法。除了对齐问题外,它还存在字节序问题。读取二进制文件的正确方法是使用uint8_t
(或unsigned char
数组,这无关紧要)以及您自己的函数来构建数据中的内存表示。
答案 3 :(得分:1)
大多数编译都提供了一个特定的扩展,允许您控制结构的打包。这应该允许你控制它。但是,当您以二进制形式编写结构时,您应该能够只编写并读取它而不管打包,就像编写结构时一样,它也应该写sizeof(struct)字节。如果您想要读取使用以前版本创建的文件,那么这将是一个麻烦的唯一情况。此外,您还需要考虑字节顺序问题等。
答案 4 :(得分:1)
您的问题是编译器特定的,但通常如果您构建结构使得每个成员位于与其自身相同大小的边界上(可被4整除的边界上的四个字节元素等),您将获得该行为你要。还要注意像你所呈现的那样的情况,其中填充位于结构的末端,以对齐下一个结构的第一个元素的开始 - 如果它们是以数组布局的。
答案 5 :(得分:1)
好像你有问题,所以我不确定为什么我甚至想回答!但是,包装很重要,并且会根据编译器版本,标志,目标架构编译指示,风向,月球相位以及可能的许多其他内容而发生变化。将二进制文件转储到文件(或套接字)并不是一种很好的序列化方法。
答案 6 :(得分:1)
当您创建这些结构的数组时,这个额外的填充是使成员正确对齐所必需的。没有它,数组的第二个元素将使 ident 成员在一个不是4的倍数的地址上对齐。
对它做任何事都可能为时已晚,你可能以前用这种结构编写了文件。更改打包将使这些文件不可读。但是,是的,拥有依赖于编译器设置的文件数据并不是最好的主意。如今,将数据以人类可读的格式存储是很常见的。磁盘字节和CPU周期都不值得。
答案 7 :(得分:0)
是的,对齐问题。这就是为什么互联网协议消息具有对齐的结构,以便在通过网络发送数据时可以避免这个问题。
您可以做的是修复结构以使它们正确对齐,或者具有在保存和检索数据时使用的编组功能。