You need to enable JavaScript to run this app. C++含__uint128_t结构体序列化反序列化时count打印为0问题排查
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.08 20:06:02