2011-03-22 164 views
33

如果我在C++中有struct,是否没有办法将它安全地读/写到跨平台/编译器兼容的文件中?C++中的结构填充

因为如果我理解正确,每个编译器都会根据目标平台进行不同的填充。

+3

通过执行二进制I/O获得的效率(性能)往往不足以证明研究,设计,开发,特别是调试和维护所花费的资金。源代码应该很容易理解,但并不简单。 – 2011-03-22 22:24:50

回答

33

不,这是不可能的。这是因为缺少C++在二进制级别的标准化。

Don Box

C++和可移植性


一旦决定要 由分发C++类的(从他的书Essential COM,章COM作为一种更好的C++报价) DLL,其中一个 面临着的一个基本问题 C++的弱点,也就是lac k的 标准化在二进制级别。 虽然ISO/ANSI C++草案 工作文件试图编纂这 程序编译和什么运行它们将 是,的 语义效果也没有试图规范 的C++二进制运行时模型。该 第一次这个问题将成为 明显的是,当一个客户端尝试对FastString DLL的导入库从 一个C++的研究与开发环境等一个用于构建 FastString DLL链接 。

结构填充由不同的编译器完成。即使您使用相同的编译器,根据您使用的pragma pack,结构的打包对齐方式也可能不同。

不仅如此,如果你写两个结构,其成员是正是一样的,唯一的不同的是,在他们声明的顺序是不同的,那么每个结构的大小可以(而且经常是)不同。

例如,看到这种情况,

struct A 
{ 
    char c; 
    char d; 
    int i; 
}; 

struct B 
{ 
    char c; 
    int i; 
    char d; 
}; 

int main() { 
     cout << sizeof(A) << endl; 
     cout << sizeof(B) << endl; 
} 

gcc-4.3.4编译它,你会得到这样的输出:

8 
12 

也就是说,大小即使这两个结构具有相同的成员有不同的!在Ideone

代码:http://ideone.com/HGGVl

的底线是,标准不谈论填充应该怎么做,所以编译器可以自由地做出任何决定,你不能承担所有的编译器做同样的决定。

+3

有'__attribute __((packed ))'我用于共享内存结构以及用于映射网络数据的结构。它确实会影响性能(请参阅http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/),但它对网络相关结构非常有用。 (就我所知,这不是一个标准,所以答案依然如此)。 – Pijusn 2015-06-08 07:11:55

+0

我不明白为什么struct A的大小是8而不是更多。 { char c; // 那这个呢? char d; // size 1 + padding of 3 int i; // size 4 }; – Dchris 2017-03-03 08:01:28

+3

@Dchris - 编译器可能很小心地确保每个字段基于自己的自然对齐进行对齐。 c和d是一个字节,因此无论您将它们放在单字节CPU指令的哪个位置,它们都是对齐的。然而,int需要在一个4字节的边界上对齐,要达到那个边界需要在d之后填充两个字节。这让你到8。 – hoodaticus 2017-05-25 20:58:22

2

您可以使用类似boost::serialization之类的东西。

6

不,没有安全的方法。除了填充之外,还必须处理不同的字节排序和不同大小的内置类型。

您需要定义一种文件格式,并将结构转换为该格式。序列化库(例如boost :: serialization或google的协议缓冲区)可以帮助解决这个问题。

+1

“结构(或类)的大小可能不等于其成员大小的总和。” – 2011-03-22 22:21:04

+0

@Thomas:没错。这只是开始的乐趣。 – Erik 2011-03-22 22:23:05

3

长话短说,没有。没有平台无关的标准符合方式来处理填充。

填充被称为在标准 “对准”,并且它开始在3.9/5讨论它:

对象类型具有对准 要求(3.9.1,3.9.2)。完整对象类型的对齐方式为 实现定义的整数 表示多个字节的值; 对象分配在地址 ,满足其对象类型的对齐要求 。

但它从那里继续前进,并转移到标准的许多黑暗角落。对齐是“实现定义的”,这意味着它可以在不同编译器之间不同,甚至在编译器的相同下的地址模型(即32位/ 64位)上不同。

除非您有严格的性能要求,否则您可能会考虑以不同的格式(如字符串)将数据存储到光盘。当自然格式可能是其他内容时,许多高性能协议都会使用字符串发送所有内容。例如,我最近处理的低延迟交换馈送发送日期为格式如下的字符串:“20110321”,时间类似地发送:“141055.200”。尽管此交换馈送整天发送500万条消息,但它们仍然使用字符串处理所有内容,因为这样可以避免排序和其他问题。

11

如果你有机会自己设计结构,它应该是可能的。基本思想是你应该设计它,这样就不需要在其中插入填充字节。第二个诀窍是你必须处理差异性。

我将介绍如何使用标量构造结构,但只要您对每个包含的结构应用相同的设计,就应该可以使用嵌套的结构。

首先,C和C++的基本事实是,类型的对齐不能超过类型的大小。如果会,那么将不可能使用malloc(N*sizeof(the_type))分配内存。

布局结构,从最大的类型开始。

struct 
{ 
    uint64_t alpha; 
    uint32_t beta; 
    uint32_t gamma; 
    uint8_t delta; 

接下来,垫了手动的结构,所以,在年底你将匹配最大的类型:

uint8_t pad8[3]; // Match uint32_t 
    uint32_t pad32;  // Even number of uint32_t 
} 

下一步是决定是否结构应存放在小或大endian格式。如果存储格式与主机系统的字节不匹配,最好的方法是在写入之前或读取结构之后,在原地10“交换”所有元素

+0

这听起来很有趣。但是,您能详细了解一下:为什么按照长度递减的顺序排列它,为什么要填充它,使得偶数个uint32_t? – Phil 2015-02-21 22:52:15

+1

@Phil,一个基本类型,比如'uint32_t',可以(可能)有一个匹配它的大小的对齐需求,在这个例子中是4个字节。编译器可以插入填充来实现这一点。通过手动执行此操作,编译器无需执行此操作,因为对齐总是正确的。缺点是在对齐要求不太严格的系统上,手动填充的结构将比编译器填充的结构大。您可以按照升序或降序完成此操作,但是如果您按升序执行int,则需要在结构体的中间插入更多垫... – Lindydancer 2015-02-22 08:34:45

+1

...仅在您需要时才需要填充结构体的结尾计划在数组中使用它。 – Lindydancer 2015-02-22 08:35:13