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

tnkciper  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(103)

在C中,我有三个内存区域,它们有几百个字节长。我想取两个内存区域的第i对32位,将它们作为两个无符号32位整数相加,并将结果存储在第三个内存区域的相应64位中(因为两个32位加法器可以产生33位结果),而不是在加法发生之前将它们存储在实际的uint32t中,而是:直接从内存中阅读它们,告诉编译器将它们视为uint32t' s,并告诉编译器将它们相加并将结果存储在第三个内存区域的前64位。
n1->bitsn2->bitsR->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.将此指针转换为(uint32t*),以便当我们执行指针算术+i时,编译器以32位为单位递增(因此,使我们获得第i个无符号32位整数)。如果没有这种转换,指针算术+i将被编译器转换为+ (i * 8) bits而不是+ (i * 32) bits,因为R->bits最初是uint8_t*
*
3.* 现在我们已经告诉编译器将R->位视为指向uint32t的指针,执行实际的指针算术+i,以获得大内存区域中的第i个32位整数。
4a.在两个ADD操作数的情况下,通过强制转换和指针运算对指针进行解引用,以读取假定的无符号32位整数的实际值。

  • (这一步4 b是我认为我的理解是错误的地方)*

4 b.在Result缓冲区的情况下,还不要解引用。首先,将指向第i个32位区域的指针转换为uint64_t*,然后解引用它并将其用作内置加法的结果,以便告诉编译器将此内置加法的结果存储在第i个32位区域的前64位,因为同样,两个32位操作数的ADD可以产生33位结果。
但是,它没有这样做。
我试着用两个操作数内存区域填充数百个1,它所做的是,它把前32个1加在一起,这应该产生:

**00000001 * 11111111 111111111 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

由于我的机器是little-endian,因此结果的内存布局应该是:

11111110 11111111 1111111 11111111 000000001
除非,当我查看结果缓冲区的内存时,它在第5个字节中缺少额外的第33个1。第五个字节全是零。这意味着当我告诉它通过转换到uint64_t*将结果缓冲区中的内存位置视为64位区域时,它拒绝听我的话。

有人能解释一下为什么吗?考虑到我的代码和我目前对它应该如何工作的理解?

h6my8fg2

h6my8fg21#

您的主要问题与指针算术无关(尽管可能涉及未定义的行为),而是与使用的类型有关。
你上面的内容相当于:

uint64_t result;
uint32_t n1, n2, carry;
// set n1, n2, and carry to some values
result = n1 + n2 + carry;

字符串
将两个uint32_t类型的值相加,结果为uint32_t类型。因此,如果结果溢出,它将简单地环绕,即。它将“修剪”除低32位以外的所有位。
您需要将其中一个参数强制转换为类型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;
            ;


此外,如果所讨论的数组的类型为uint8_t,并且存储在R->bits中的64位值可能彼此重叠,则存在严格的别名冲突。
你想要的完全兼容的版本看起来像这样:

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

scyqe7ek

scyqe7ek2#

您的代码调用未定义的行为。指针双关违反了严格的别名规则。您需要使用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);
}

zlhcx6iw

zlhcx6iw3#

我发现错误在哪里:在解引用两个ADD操作数的两个内存位置以获得实际的无符号整数值之后,另外将它们转换为(uint64_t)。同时将carry的类型也更改为uint64_t。现在,整个加法只使用uint64_t完成,并按预期工作。

相关问题