cpointersmemorytype-conversionpointer-arithmetic

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


In C, I have three memory areas that are several hundred bytes long. I want to take the ith pair of 32 bits of the two memory areas, add them as two unsigned 32-bit integers, and store the result in the respective 64 bits of the third memory area (because two 32-bit ADDs can produce a 33-bit result), without storing them in actual uint32t's before addition happens, rather: directly reading them from memory, telling the compiler to treat them as uint32t's, and also telling the compiler to add them up and store the result in the first 64 bits of the third memory area.

n1->bits, n2->bits and R->bits are the pointers to my three memory areas respectively. Originally their types are uint8_t*

I have made sure that the three memory areas' sizes are divisible by 32.

Note: carry is an actual variable of type uint32_t, and shouldn't be needing attention when answewring.

THE PROBLEM: For some reason the compiler does read them as two unsigned 32-bit integers, but refuses to store their result in the first 64 bits of the Result memory area, instead it overflows. Here is how I'm doing it:

*((uint64_t*)( ((uint32_t*)(R->bits)) + i)) = 
            *( ((uint32_t*)(n1->bits)) + i)
            +
            *( ((uint32_t*)(n2->bits)) + i)
            +
            carry;
            ;

Here is my current understanding of how this code should work. Correct me where I'm wrong please:

1. Get to the first address of the memory area using R->bits

2. Cast this pointer to a (uint32t*) so that when we do the pointer arithmetic +i, the compiler increments by units of 32 bits (thus, getting us to the i-th unsigned 32-bit integer). Without this cast, the pointer arithmetic +i would have been translated to + (i * 8) bits instead of + (i * 32) bits by the compiler, since R->bits is originally a uint8_t* .
*
3. *Now that we've told the compiler to treat R->bits as a pointer to a uint32t, do the actual pointer arithmetic +i in order to get to the i-th 32-bit integer in the large memory areas.

4a. In the case of the two ADD operands, dereference the pointer you got to via the casts and the pointer arithmetic to read the actual values of the supposed unsigned 32-bit integers there.

(This step 4b is where I think my understanding is wrong)
4b. In the case of the Result buffer, don't dereference yet. First, cast the pointer to the i-th 32-bit region to a uint64_t* and then dereference it and use it as the result of built-in addition, in order to tell the compiler to store the result of this built-in addition in the first 64 bits of the i-th 32-bit region because, again, an ADD on two 32-bit operands can produce a 33-bit result.

Except, it doesn't do it.

I tried it with both operand memory areas filled with hundreds of 1s, and what it did was, it took the first 32 ones, added them together, which should have produced:
00000001 11111111 11111111 11111111 11111110

And since my machine is little-endian, the memory layout of the result should have been:
11111110 11111111 11111111 11111111 00000001

EXCEPT, when I looked at the memory of the result buffer, it was missing that extra 33rd 1 in the 5th byte. The 5th byte was all zeroes. Which means it refused to listen to me when I told it to treat the memory location it go to in the result buffer as a 64-bit zone via that cast to a uint64_t* .

Can someone explain why? Given my code and my current understanding of how it should to be working?


Solution

  • Your main problem is not related to the pointer arithmetic (although there is likely undefined behavior involved), but to the types in use.

    What you have above is equivalent to:

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

    When you add two values with type uint32_t together, the result has type uint32_t. So if the result oveflows it will simply wrap around, i.e. it will "trim" all but the low 32 bits.

    You need to cast one of the arguments to type uint64_t so that the addition is done with that type, i.e.:

    result = (uint64_t)n1 + n2 + carry;
    

    Or, going back to your code:

    *((uint64_t*)( ((uint32_t*)(R->bits)) + i)) = 
                (uint64_t)*( ((uint32_t*)(n1->bits)) + i)
                +
                *( ((uint32_t*)(n2->bits)) + i)
                +
                carry;
                ;
    

    Also, you have a strict aliasing violation if the arrays in question have type uint8_t, and the 64 bit values stored in R->bits are likely overlapping each other.

    A fully compliant version of what you want would look like this:

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