我正在研究一个C ++程序,它有充分的理由(1)需要存储在磁盘上的二进制数据格式。编写数据是任意struct
个条目。
我的程序同时具有32位和64位版本,并且二进制数据文件可以由一个写入并由另一个读取。这意味着存储结构的字段必须是具有可预测大小和对齐的类型,以便两个自然字大小的结果布局相同。
我担心未来的维护者可能会在没有真正思考的情况下添加int
,或者在uint32_t
之后紧跟一个uint64_t
之后意外违反这一点。< / p>
有没有办法进行编译时检查(即static_assert
)结构将在32位和64位系统上相同布局?如果前者不可能进行运行时检查呢?
从概念上讲,我认为会是这样的:
for (every field):
static_assert: sizeof_32(field) == sizeof_64(field)
static_assert: offset_of(next_field) == offset_of(field) + sizeof(field)
或更简单:
static_assert: sizeof_32(struct) == sizeof_64(struct)
鉴于程序正在针对两种位大小进行编译,从技术上讲,仅在一种架构上进行断言是可以的,因为这仍然会暴露问题。
如果被检查的结构受到某种程度的限制(例如需要显式的填充字段),只要它可以保证正确,它也没关系。
答案 0 :(得分:1)
这是我能想到的最接近“自动”的东西:
对于将在此持久性二进制数据中使用的所有结构,添加具有预期实例大小的属性。
struct MyPersistentBinaryStructure {
// Expected size for 32/64-bit check.
static constexpr size_t kExpectedInstanceSize = 80;
... 80 bytes of fixed size fields and appropriate padding ...
};
然后,在查找该二进制数据中结构地址的代码中,检查该值:
template <typename T>
T* GetAsObject(Reference ref) {
static_assert(std::is_pod<T>::value, "only simple objects");
static_assert(T::kExpectedInstanceSize == sizeof(T), "inconsistent size");
return reinterpret_cast<T*>(GetPointerFromRef(ref));
}
将结构编译为不同大小的任何构建都会产生编译时错误。这不会构建未来的版本,因为宽度为X的定义不会被捕获,直到它实际构建在宽度为X的体系结构上,但至少你会知道并且可能能够适应结构没有打破格式(例如32位int
- &gt; int32_t
)。
这样做是值得的,因为它立即在代码中发现了三个32/64不兼容性,我会非常小心地手动检查。其中两个错误会导致数据损坏;另一个只是一些额外的尾垫。
答案 1 :(得分:0)
这可能更像是一个黑客而不是一个答案,但我相信你可以使用这样的东西(这将使任何未来的维护者更清楚):
#include <limits.h>
#if ULONG_MAX == (0xffffffffffffffffUL) // 64 bit code here
// ...
#elif ULONG_MAX == (0xffffffffUL) // 32 bit code here
// ...
#else
#error unsupported
#endif
P.S。
说完了......我会避免在写文件时直接使用结构。
有太多可能出错的地方,除了文件膨胀之外(结构被填充,意味着你将在文件中获得大量任意垃圾数据)。
最好使用序列化函数,分别存储和加载每个字段,逐字节(或逐位)执行,避免出现32/64位和字节序等问题。
修改强>:
我看到了关于为IO使用映射文件的评论......有点像数据库实现。
在这种情况下,我可能会在struct的代码中找到明确的注释,并且所有字段(如果可能)都是bit-size explicit或unions。即:
// this type is defined to make sure pointer sizes are the same for
// 64bit and 32bit systems.
typedef union {
void *ptr;
char _space[8];
} pntr_field;
struct {
size_t length : 32; // explicit bit count for 64bit and 32bit compatibility
size_t number : 32;
pntr_field my_ptr; // side-note: I would avoid pointers,
size_t offset : 32; // side-note: offsets are better for persistence.
} my_struct;
然而......即使在这种情况下,假设文件可以跨系统传输,我可能会使用带有“根”偏移样式指针的getter / setter函数。
这将允许数据被压缩(避免结构填充问题)并允许映射文件的不断变化的内存地址(因为每个程序重启,所有指针都将变为无效以及我们真正关心的是什么将是数据相对于文件或对象的根的偏移量... ...
祝你好运!