C语言中32位无符号整数内存直接相加存64位内存溢出问题
需求背景
有三个uint8_t*类型指针n1->bits、n2->bits、R->bits指向数百字节的内存区域,需完成以下操作:
- 从
n1和n2中取出第i对32位数据,视为uint32_t无符号整数 - 将两个数相加(含
uint32_t类型的carry),结果存入R中对应位置的连续64位空间(容纳32位相加可能产生的33位结果) - 要求直接通过内存操作完成,不借助临时
uint32_t变量
现有实现代码
*((uint64_t*)( ((uint32_t*)(R->bits)) + i)) = *( ((uint32_t*)(n1->bits)) + i) + *( ((uint32_t*)(n2->bits)) + i) + carry;
对代码逻辑的理解(用户视角)
- 通过
R->bits获取结果内存区域的首地址
- 通过
- 将指针强制转换为
uint32_t*,确保指针算术+i时以32位(4字节)为单位递增,定位到第i个32位整数的起始位置;若不转换,uint8_t*的+i仅会偏移i个字节,无法正确定位目标
- 将指针强制转换为
- 转换为
uint32_t*后执行+i,得到第i个32位整数的起始地址
- 转换为
- 4a. 对两个操作数,通过强制转换和指针算术得到的指针解引用,直接读取对应位置的32位无符号整数
- 4b. 对结果缓冲区,将指向第i个32位区域的指针强制转换为
uint64_t*,再解引用赋值,期望编译器将加法结果存入该起始地址后的连续64位空间,容纳32位相加产生的进位
实际问题现象
测试时将两个操作数内存区域填充为数百个0x01字节,第一个32位数据相加后预期得到64位结果:0x00000001FFFFFFFE(二进制为00000001 11111111 11111111 11111111 11111110)。
由于机器是小端序,预期内存布局为:0xFE 0xFF 0xFF 0xFF 0x01(后续字节为0)。但实际查看结果缓冲区时,第5个字节(对应64位结果的高位)全为0,加法结果发生溢出,高位进位丢失。
问题原因解析
1. 加法运算结果被截断为32位
代码中,*(uint32_t*)...和carry都是uint32_t类型,根据C语言的整数运算规则,多个uint32_t相加的结果类型仍然是uint32_t。这意味着即使相加结果超过32位,超出的高位会被无符号溢出自动截断,仅保留低32位值。之后将这个截断后的值赋值给uint64_t*指向的空间时,编译器会自动将uint32_t零扩展为uint64_t,高位字节自然全部为0,这就是你看到第5个字节为0的核心原因。
2. 指针转换的逻辑误区
你错误地认为将uint32_t*强制转为uint64_t*就能让加法结果以64位形式存入,但问题的根源不在赋值的目标类型,而在加法运算本身的结果类型。无论目标是uint64_t还是其他类型,只要右边的表达式结果是uint32_t,就只能得到被截断后的值,无法保留进位。
正确实现思路
要解决这个问题,需要在加法运算前将操作数提升为uint64_t类型,确保运算结果是64位,完整保留进位:
*(((uint64_t*)R->bits) + i) = (uint64_t)*((uint32_t*)n1->bits + i) + (uint64_t)*((uint32_t*)n2->bits + i) + carry;
这里先把每个32位操作数强制转换为uint64_t,加法运算的结果自然是uint64_t类型,能完整保留32位相加产生的进位,再赋值给uint64_t*指向的空间,即可得到正确的64位结果。
注:需确保
R->bits的地址满足uint64_t的对齐要求,否则强制转换为uint64_t*可能触发未定义行为。
内容的提问来源于stack exchange,提问作者Kevin Stefanov

