在c + +结构填充
如果我有一个C ++的struct
,是否没有办法安全地读/写一个跨平台/编译器兼容的文件? 因为如果我理解正确的话,每个编译器都会根据目标平台的不同来填充。
不,那是不可能的。 这是因为C ++在二进制级别上缺乏标准化 。
Don Box写道(从他的书Essential COM中引用COM作为更好的C ++一章)
C ++和可移植性
一旦决定将C ++类作为DLL来分发,就会面临C ++的一个基本弱点 ,那就是在二进制级缺乏标准化 。 尽pipeISO / ANSI C ++草案工作文件试图编译哪些程序将被编译以及运行它们的语义效果是什么, 但它并没有尝试标准化C ++的二进制运行时模型 。 第一次这个问题会变得很明显,当客户端试图从一个C ++开发环境(用于构buildFastString DLL的环境) 之外的FastString DLL的导入库链接时。
struct-padding由不同的编译器完成。 即使您使用相同的编译器,根据您使用的编译包,包装alignment的结构也可能不同。
不仅如此,如果你写了两个成员完全相同的结构体, 唯一的区别是它们声明的顺序是不同的,那么每个结构体的大小可以是(通常是)不同的。
例如,看到这个,
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
底线是标准不会讨论如何进行填充,所以编译器可以自由地做出任何决定,而且不能假定所有的编译器都做出相同的决定。
如果你有机会自己devise结构,它应该是可能的。 基本的想法是你应该devise它,这样就不需要在其中插入填充字节。 第二个诀窍是你必须处理差异性。
我将描述如何使用标量构造结构,但是只要您对每个包含的结构应用相同的devise,您就应该可以使用嵌套的结构。
首先,C和C ++的一个基本事实就是一个types的alignment不能超过这个types的大小。 如果是这样,那么使用malloc(N*sizeof(the_type))
分配内存是不可能的。
布局结构,从最大的types开始。
struct { uint64_t alpha; uint32_t beta; uint32_t gamma; uint8_t delta;
接下来,手动填充结构,以便最后匹配最大的types:
uint8_t pad8[3]; // Match uint32_t uint32_t pad32; // Even number of uint32_t }
下一步是确定结构是否应该以小或大端格式存储。 如果存储格式与主机系统的字节不匹配,最好的方法是在写入之前或读取结构之后, 在原处 “交换”所有的元素。
不,没有安全的方法。 除了填充之外,还必须处理不同的字节顺序,以及不同大小的内置types。
您需要定义一个文件格式,并将结构转换为该格式。 序列化库(例如boost :: serialization或google的协议缓冲区)可以帮助解决这个问题。
长话短说,没有。 没有平台无关的,符合标准的方式来处理填充。
在标准中,填充称为“alignment”,并开始在3.9 / 5中讨论:
对象types有alignment要求(3.9.1,3.9.2)。 完整对象types的alignment是实现定义的整数值,表示多个字节; 一个对象被分配在满足其对象types的alignment要求的地址处。
但是从那里开始,风向标准的许多黑暗angular落。 alignment是“实现定义的”,这意味着在不同的编译器之间,甚至在同一个编译器下的地址模型(即32位/ 64位)上,它们可能是不同的。
除非您有严格的性能要求,否则可以考虑将数据以不同的格式存储到光盘中,如string。 当自然格式可能是别的东西的时候,许多高性能的协议都会使用string来发送。 例如,我最近处理的低延迟交换馈送发送date为格式如下的string:“20110321”,时间发送类似:“141055.200”。 尽pipe这个交换饲料每天发送500万条消息,但它们仍然使用string,因为这样可以避免sorting和其他问题。
你可以使用诸如boost::serialization
类的东西。