如果我在C++中有一个struct
,是否没有办法安全地将其读写到跨平台/编译器兼容的文件中?
因为如果我理解正确,每个编译器根据目标平台以不同的方式'填充'。
不可能。这是因为C++在二进制级别上缺乏标准化导致的。
唐·瑟曼在他的书 Essential COM 中引用了一段话,其中写到:
C++和可移植性
一旦决定将一个C++类作为DLL分发, 就会面临C++的一个基本弱点, 即缺乏二进制级别上的标准化。 虽然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
也就是说,尽管两个结构体具有相同的成员,但它们的大小是不同的!
归根结底,标准没有规定如何进行填充,因此编译器可以自由决定,你不能假设所有编译器都会做出相同的决定。
__attribute__((packed))
这个东西,我用来处理共享内存结构以及映射网络数据的结构。它会影响性能(参见http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/),但对于与网络相关的结构体来说是一个有用的特性。(据我所知,这不是标准,因此答案仍然正确)。 - PijusnA::c
和 A::d
之间放置填充?如果没有,那么我说问题只是标准没有做出任何保证,尽管每个编译器似乎都在做着相同的事情(就像 reinterpret_cast
一样)。 - Indiana Kernick 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
}
下一步是决定结构体应该以小端或大端格式存储。最好的方法是在写入结构体之前或读取结构体之后,如果存储格式与主机系统的字节序不匹配,则在原地“交换”所有元素。
uint32_t
这样的基本类型可能具有与其大小相匹配的对齐要求,在这种情况下为四个字节。编译器可能会插入填充以实现此目的。通过手动执行此操作,编译器将不需要执行此操作,因为对齐始终正确。缺点是在具有较少严格对齐要求的系统上,手动填充的结构将比由编译器填充的结构更大。您可以按升序或降序进行此操作,但如果按升序进行,则需要在结构体中间插入更多的填充。 - Lindydancermalloc(2*sizeof(a_type))
(或new[]
)将不会返回一个可以访问两个元素的数组。在给定的系统上,std::max_align_t
是最高对齐标量(如long double
)的typedef。如果它是一个typedef到具有较低对齐的标量,则C++实现将被破坏。但是,如果您能提供一个sizeof(type) < alignof(type)
成立的单个示例,那么请证明我是错误的。 - Lindydancer不,没有安全的方法。除了填充之外,您还需要处理不同的字节顺序和不同内置类型的大小。
您需要定义文件格式,并将结构体转换为该格式。序列化库(例如boost::serialization或Google的protocolbuffers)可以帮助完成此任务。