提问人:Kevin Stefanov 提问时间:7/26/2023 更新时间:7/27/2023 访问量:95
C:从原始内存字节添加两个 32 位无符号整数
C: Adding two 32-bit unsigned integers from raw memory bytes
问:
在 C 语言中,我有三个几百字节长的内存区域。我想取两个内存区域的第 32 对 32 位,将它们加为两个无符号的 32 位整数,并将结果存储在第三个内存区域的相应 64 位中(因为两个 32 位 ADDS 可以产生 33 位结果),而不是在加法发生之前将它们存储在实际的 中, 相反:直接从内存中读取它们,告诉编译器将它们视为 S,并告诉编译器将它们相加并将结果存储在第三个内存区域的前 64 位中。i
uint32t
uint32t'
n1->bits
,分别是指向我的三个记忆区域的指针。最初它们的类型是n2->bits
R->bits
uint8_t*
我已确保三个内存区域的大小可以被 32 整除。
注意:是 类型的实际变量,在查询时不需要注意。carry
uint32_t
问题:由于某种原因,编译器确实将它们读取为两个无符号的 32 位整数,但拒绝将它们的结果存储在 Result 内存区域的前 64 位中,而是溢出。这是我是如何做到的:
*((uint64_t*)( ((uint32_t*)(R->bits)) + i)) =
*( ((uint32_t*)(n1->bits)) + i)
+
*( ((uint32_t*)(n2->bits)) + i)
+
carry;
;
这是我目前对这段代码应该如何工作的理解。请纠正我错的地方:
1.使用获取内存区域的第一个地址R->bits
2. 将此指针转换为 a,以便当我们进行指针运算时,编译器以 32 位为单位递增(因此,得到第 i 个无符号 32 位整数)。如果没有这种强制转换,指针算术将被转换为而不是由编译器转换,因为最初是一个 .
*
3.*现在我们已经告诉编译器将 R->bit 视为指向 a 的指针,请执行实际的指针运算,以便在大内存区域中获取第 i 个 32 位整数。(uint32t*)
+i
+i
+ (i * 8) bits
+ (i * 32) bits
R->bits
uint8_t*
uint32t
+i
4一个。对于两个 ADD 操作数,取消引用通过强制转换和指针算术获得的指针,以读取其中假定的无符号 32 位整数的实际值。
(这一步 4b 是我认为我的理解错误的地方)
4b。对于 Result 缓冲区,请不要取消引用。首先,将指向第 i 个 32 位区域的指针转换为 a,然后取消引用它并将其用作内置加法的结果,以便告诉编译器将此内置加法的结果存储在第 i 个 32 位区域的前 64 位中,因为同样,两个 32 位操作数上的 ADD 可以生成 33 位结果。uint64_t*
除了,它不这样做。
我尝试了两个充满数百个 1 的操作数内存区域,它所做的是,它把前 32 个 1 加在一起,应该产生:
00000001 11111111 11111111 11111111 11111110
由于我的机器是 little-endian,因此结果的内存布局应该是:
11111110 11111111 11111111 11111111 00000001
除了,当我查看结果缓冲区的内存时,它在第 5 个字节中缺少额外的第 33 个 1。第 5 个字节全为零。这意味着当我告诉它通过转换为 .uint64_t*
有人可以解释为什么吗?鉴于我的代码和我目前对它应该如何工作的理解?
答:
您的主要问题与指针算术无关(尽管可能涉及未定义的行为),而是与使用的类型有关。
您上面所拥有的等同于:
uint64_t result;
uint32_t n1, n2, carry;
// set n1, n2, and carry to some values
result = n1 + n2 + carry;
将两个 type 的值相加时,结果的 type 为 。因此,如果结果 oveflow,它将简单地环绕,即它将“修剪”除低 32 位之外的所有位。uint32_t
uint32_t
您需要将其中一个参数转换为类型,以便使用该类型完成添加,即:uint64_t
result = (uint64_t)n1 + n2 + carry;
或者,返回到您的代码:
*((uint64_t*)( ((uint32_t*)(R->bits)) + i)) =
(uint64_t)*( ((uint32_t*)(n1->bits)) + i)
+
*( ((uint32_t*)(n2->bits)) + i)
+
carry;
;
此外,如果所讨论的数组具有 type ,并且存储在 中的 64 位值可能相互重叠,则存在严格的别名冲突。uint8_t
R->bits
您想要的完全兼容版本如下所示:
uint64_t result;
uint32_t v1, v2;
memcpy(&v1, n1->bits + sizeof(v1) * i, sizeof(v1));
memcpy(&v2, n2->bits + sizeof(v2) * i, sizeof(v2));
result = (uint64_t)v1 + v2 + carry;
memcpy(R->bits + sizeof(result) * i, &result, sizeof(result));
评论
(uint32_t*)(R->bits)) + i)
i
uint8_t
uint32_t
uint64_t
uint8_t
unsigned char
代码调用未定义的行为。指针双关违反了严格的别名规则。您需要使用或访问。memcpy
char
void add3264(void *src1, void *src2, void *dest)
{
uint32_t a,b;
uint64_t result;
memcpy(&a, src1, sizeof(a));
memcpy(&b, src1, sizeof(b));
result = (uint64_t)a + b;
memcpy(dest, &result, sizeof(result));
}
或
uint32_t get32(void *ptr)
{
unsigned char *ucptr = ptr;
return ucptr[0] + (uint32_t)ucptr[1] << 8 + (uint32_t)ucptr[2] << 16 + (uint32_t)ucptr[3] << 24;
}
void write64(void *ptr, uint64_t val)
{
unsigned char *uc = ptr;
for(int index = 0; index < sizeof(val); index++)
{
*uc++ = val;
val >>= 8;
}
}
void add3264_1(void *src1, void *src2, void *dest)
{
uint32_t a,b;
uint64_t result;
a = get32(src1);
b = get32(src2);;
result = (uint64_t)a + b;
write64(dest, result);
}
我发现了错误所在:在取消引用两个ADD操作数的两个内存位置以获取实际的无符号整数值后,另外将它们转换为(uint64_t)。同时将 的 type 更改为 also be uint64_t。现在,整个添加仅使用uint64_t完成,并按预期工作。carry
评论
n1, n2, R
uint32_t, uint32_t, uint64_t
n1, n2, R
*( ((uint32_t*)(n2->bits)) + i)
可以写成((uint32_t*)(n2->bits))[i]