C pointers, playing around with casting

Viewed 123

I stumbled on some code in the project I'm working on and I wanted to be sure to understand it correctly. So here it is:

uint16_t* tmp;

tmp = (uint16_t*) ((uint8_t*)getVariableAddress(variable) + offset);

tmp = (uint16_t*)((uint8_t*)tmp + otherOffset);
Set_Register((unsigned long) tmp[0]);
Set_OtherRegister((unsigned long) tmp[2]);

At first I got a bit lost between all the casts but the way I see it the uint8_t* are being used to move byte per byte and add the offset values to the base address we place in tmp, this was the first part that got me troubled. The second part was the use of [] on a uint16_t* , for this one I'm not sure at all on the result, anyone care to explain this in detail ?

Thanks

3 Answers

I think it is easier to see what happens if we rewrite the code a bit:

// We keep this as a `uint8_t *` so we can add offsets correctly.
uint8_t *base_address = (uint8_t *)getVariableAddress(variable);

// Add the offsets to the base address.
uint8_t *offsetted_address = base_address + offset + otherOffset;

// We want it as a uint16_t.
uint16_t *as_u16 = (uint16_t *)offsetted_address;

// We want to write the first register with the first `uint16_t` at `offsetted_address`, but `Set_Register` takes the value as `unsigned long`.
unsigned long first_u16 = as_u16[0];
Set_Register(first_u16);

// We want to write the other register with the third `uint16_t` at `offsetted_address`, but `Set_OtherRegister` takes the value as `unsigned long`.
unsigned long third_u16 = as_u16[2];
Set_OtherRegister(third_u16 );

We know that the values that interest us are at an offset relative to the address returned by getVariableAddress. In order to properly compute that address we cast the address to an uint8_t. If we keep as uint16_t our arithmetic would be wrong. Consider this:

uint8_t *p = (uint8_t *)0x100;
printf("%p\n", p + 1); // prints 0x101

uint16_t *q = (uint16_t *)0x200;
printf("%p\n", q + 1); // prints 0x202

We then want to read the first and third 16-bit unsigned integer from the address we just computed, so we cast it to an uint16_t * and get the first ([0]) and third ([2]) integers.

When you use something such as:

int var[5];
var[3] = 1;

When declaring var, it allocates 5 integers contiguously in memory, and var gets just a pointer to the first of those 5 memory slots, essentially an int*.

Then when you access it with var[3] you are telling it to access that first memory address with an offset of 3 times sizeof(int).

In your example it's doing the same thing, you are getting the pointer to the first memory position that tmp is pointing to, and then adding an offset of a value to it

otherOffset is in units of bytes, but tmp is a uint16_t *. If the address were calculated as tmp + otherOffset, the sum would be calculated by treating otherOffset as a number of uint16_t objects rather than a number of bytes. So, to do the desired calculation, tmp is converted to uint8_t *. Then (uint8_t *) tmp + otherOffset does the calculation in units of bytes.

Similarly, offset is in units of bytes, and the type of getVariableAddress(variable) might not be a pointer to a byte/character type (we do not know since you have not shown us its declaration), so the cast ensures the arithmetic is done in the desired units, bytes.

The casts to uint16_t * merely convert the results of arithmetic to the desired pointer type for further use.

tmp[0] and tmp[2] are ordinary uses of subscript. In effect, they say, “There is an array of uint16_t starting at the location tmp points to. Give me the element with index 0 or 2 from that array.” Formally, tmp[i] is defined as *(tmp + i), which says to add the offset i to tmp and then dereference the resulting address.

Related