C:从原始内存字节添加两个 32 位无符号整数

C: Adding two 32-bit unsigned integers from raw memory bytes

提问人:Kevin Stefanov 提问时间:7/26/2023 更新时间:7/27/2023 访问量:95

问:

在 C 语言中,我有三个几百字节长的内存区域。我想取两个内存区域的第 32 对 32 位,将它们加为两个无符号的 32 位整数,并将结果存储在第三个内存区域的相应 64 位中(因为两个 32 位 ADDS 可以产生 33 位结果),而不是在加法发生之前将它们存储在实际的 中, 相反:直接从内存中读取它们,告诉编译器将它们视为 S,并告诉编译器将它们相加并将结果存储在第三个内存区域的前 64 位中。iuint32tuint32t'

n1->bits,分别是指向我的三个记忆区域的指针。最初它们的类型是n2->bitsR->bitsuint8_t*

我已确保三个内存区域的大小可以被 32 整除。

注意:是 类型的实际变量,在查询时不需要注意。carryuint32_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) bitsR->bitsuint8_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*

有人可以解释为什么吗?鉴于我的代码和我目前对它应该如何工作的理解?

c 内存 类型转换 指针-算术

评论

0赞 chux - Reinstate Monica 7/27/2023
“我已经确保三个内存区域的大小可以被 32 整除”是好的,但还不够。内存区域的基址满足 的对齐要求也很重要。为了更好地回答这部分问题,发布代码显示 .n1, n2, Ruint32_t, uint32_t, uint64_tn1, n2, R
0赞 ikegami 7/27/2023
*( ((uint32_t*)(n2->bits)) + i)可以写成((uint32_t*)(n2->bits))[i]

答:

0赞 dbush 7/27/2023 #1

您的主要问题与指针算术无关(尽管可能涉及未定义的行为),而是与使用的类型有关。

您上面所拥有的等同于:

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_tuint32_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_tR->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));

评论

0赞 Kevin Stefanov 7/27/2023
你一贴出答案,我就发现了我的错误哈哈,我们俩都得出了同样的结论。至于混叠冲突,没有什么是相互重叠的,因为我在相同的两个内存区域上执行了多个 32 位 + 32 位 ADD,将每个结果存储在各自的 Result 缓冲区中。我不必担心这里重叠的原因是,这是在 base 2^32 中工作的 BigNum 加法实现的一部分,因此添加了单独的 32 位分支。我有处理剩余的第 33 位的逻辑,这是位于 64 位区域后半部分的唯一位。它有效。
0赞 dbush 7/27/2023
@KevinStefanov 的连续值相隔 4 个字节,因此,如果您在那里写入 64 位值,它们实际上会重叠。发生别名冲突的原因是您正在访问类型为 或 的 as 值数组。“它有效”是无论如何都不能保证的。(uint32_t*)(R->bits)) + i)iuint8_tuint32_tuint64_t
1赞 Ian Abbott 7/27/2023
好吧,可能存在严格的混叠冲突;这取决于实现如何映射类型。如果它映射到扩展的无符号整数类型,则会出现严格的别名冲突。如果它被映射到,则不会出现严格的别名冲突。但是可移植程序不应该假设这一点。uint8_tunsigned char
0赞 dbush 7/27/2023
@IanAbbott 只有当你要从另一个人那里转到那个类型时。该类型转到另一种类型仍然是严格的别名违规。
0赞 dbush 7/27/2023
@IanAbbott 考虑到运算用例,似乎内存缓冲区应该被声明为适当的类型。
0赞 0___________ 7/27/2023 #2

代码调用未定义的行为。指针双关违反了严格的别名规则。您需要使用或访问。memcpychar

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);
}
-1赞 Kevin Stefanov 7/27/2023 #3

我发现了错误所在:在取消引用两个ADD操作数的两个内存位置以获取实际的无符号整数值后,另外将它们转换为(uint64_t)。同时将 的 type 更改为 also be uint64_t。现在,整个添加仅使用uint64_t完成,并按预期工作。carry

评论

0赞 0___________ 7/27/2023
你很糟糕,因为它可能会引发未定义的行为。永远不要这样做。*(uintxx_t *)
0赞 Community 8/1/2023
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。