C++含__uint128_t结构体序列化反序列化时count打印为0问题排查
包含__uint128_t的POD字段序列化时printf打印count值异常问题
场景与复现代码
尝试对包含__uint128_t类型的结构体逐字段做序列化/反序列化(不依赖整体结构体的sizeof计算长度),复现代码如下:
typedef __uint128_t data_t; //typedef uint64_t data_t; struct Test { data_t element; int count; Test()=default; }; template <typename POD> std::ostream& serialize(std::ostream& os, const POD& v) { static_assert(std::is_trivial<POD>::value && std::is_standard_layout<POD>::value, "Can only serialize POD types with this function"); printf("in POD, sizeof(POD): %lu\n", sizeof(v)); os.write(reinterpret_cast<const char*>(&v), sizeof(POD)); return os; } template <typename POD> std::istream& deserialize(std::istream& is, POD& v) { static_assert(std::is_trivial<POD>::value && std::is_standard_layout<POD>::value, "Can only deserialize POD types with this function"); is.read(reinterpret_cast<char*>(&v), sizeof(POD)); return is; } int main() { std::string tmp_buffer; std::stringstream oss; std::vector<Test>data_in(2); data_in[0].element=2345; data_in[0].count=4; data_in[1].element=5431; data_in[1].count=10; serialize(oss, data_in[0].element); serialize(oss, data_in[0].count); serialize(oss, data_in[1].element); serialize(oss, data_in[1].count); tmp_buffer = oss.str(); printf("buffer len: %lu\n", tmp_buffer.size()); Test k1; deserialize(oss, k1.element); deserialize(oss, k1.count); printf("data_out0: element: %llu, count: %d\n", k1.element, k1.count); Test k2; deserialize<data_t>(oss, k2.element); deserialize<int>(oss, k2.count); printf("data_out1: element: %llu, count: %d\n", k2.element, k2.count); assert(data_in[0].element == k1.element); assert(data_in[0].count == k1.count); /* passes ? */ int c=k1.count; Test x = Test{k1.element, c}; printf("c: %d, element: %llu, count: %d\n", c, x.element, x.count); return 0; }
实际运行输出
in POD, sizeof(POD): 16 in POD, sizeof(POD): 4 in POD, sizeof(POD): 16 in POD, sizeof(POD): 4 buffer len: 64 data_out0: element: 2345, count: 0 data_out1: element: 5431, count: 0 c: 4, element: 2345, count: 0
核心疑问
- 为什么element字段的打印值符合预期,但经过序列化/反序列化正确读回的count值,用printf打印时始终显示为0?
- 将data_t从
__uint128_t替换为uint64_t后,所有字段打印正常的根本原因是什么?
根因解析
1. count值打印为0的本质:可变参数的寄存器传递规则
x86_64 Linux系统下遵循System V AMD64 ABI规范,对可变参数函数(比如printf)的参数传递有明确的寄存器占用规则:
- 函数调用时,格式字符串作为第一个固定参数放入
rdi寄存器,后续整数类型的可变参数依次放入rsi、rdx、rcx、r8、r9寄存器,超出寄存器容量的部分才会放入栈空间。 - 对于大小超过8字节、对齐要求为16字节的
__uint128_t类型,ABI规定它需要占用连续的两个相邻整数寄存器(比如传入位置本来只占rsi的话,16字节的__uint128_t会同时占用rsi和rdx两个寄存器来存储完整的128位值)。
回到代码里的printf调用:
printf("data_out0: element: %llu, count: %d\n", k1.element, k1.count);
- 因为
k1.element是16字节的__uint128_t类型,调用时它会占用rsi(存128位值的低64位)和rdx(存128位值的高64位)两个寄存器。传入的element值是很小的整数(2345、5431),高64位全为0,低64位存了实际数值。 - 本来应该作为第二个可变参数的4字节
int类型的k1.count,因为rdx寄存器已经被128位值的高64位占了,按照ABI规则会被挤到rcx寄存器位置。 - printf内部解析格式符时,完全按照标准8字节参数的偏移读取:解析
%llu时从rsi位置读8字节,刚好拿到element的低64位值,所以element打印正常;解析%d时按照偏移去本来应该放第二个整数参数的rdx位置读4字节,此时rdx里存的是element的高64位(值为0),根本不是count的实际值,所以打印出来count始终是0。
至于为什么assert校验可以通过:assert是宏展开后的强类型对比,直接从变量的内存地址读取真实值,完全不经过可变参数的寄存器传递流程,所以k1.count在内存里存的是正确的4、10,对比时不会出现偏移,校验自然可以通过。
2. 替换为uint64_t后异常消失的原因
当data_t替换为8字节、对齐要求8字节的uint64_t类型时:
- 16字节的寄存器占用场景不存在了,8字节的
uint64_t只需要占用rsi一个寄存器位置就能完整存下值。 - 后续的
k1.count参数会按照正常偏移放入rdx寄存器位置,printf解析%llu读rsi、解析%d读rdx,参数位置完全匹配,所有字段的打印值自然可以正常显示。
常见修正方式
打印__uint128_t类型值时,不要直接把它作为可变参数传入printf,先将其转换为与格式符匹配的8字节类型,避免挤占连续两个寄存器导致后续参数偏移:
printf("data_out0: element: %llu, count: %d\n", (unsigned long long)k1.element, k1.count);
内容的提问来源于stack exchange,提问作者PGOnTheGo
相关产品推荐
相关产品推荐

