Splaty Splaty - 22 days ago 8
C Question

C pointer cast - value truncation

The Question



Here's the code that casts a pointer to 16-bit value into a pointer to 32-bit value:

int low_level_read(uint32_t * read_data)
{
// some low level access to get 32-bit read here
}

int i2c_read(uint16_t * data)
{
low_level_read((uint32_t *) data);

printf("data=0x%X\n", *data);
}


Expected:


  • low_level_read obtains: 0x0000C101

  • i2c_read obtains: 0xC101



Observed:


  • low_level_read obtains: 0x0000C101

  • i2c_read obtains: 0x0000



Why does it seem like it's truncating/cutting-off the least significant 16 bits?

My Solution to this problem



If the i2c_read() is modified to look like below, then this works as expected:

int i2c_read(uint16_t * data)
{
uint32_t raw_data;
low_level_read(raw_data);

*data = (uint16_t) raw_data;
}


That's fine but I would still like to understand why the first piece of code is acting like that.

My Educated Guess as to Why



When we pass in the pointer to i2c_read(), it was meant for 8-bits:

pointer address 0x100 ->
+--+--+--+--+--+--+--+--+
7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+


However when cast to (uint32_t *), it "grows" the size of what the memory location could hold to 32-bits:

pointer address 0x100 ->
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+
31 30 29 28 27 26 25 24 .... 7 6 5 4 3 2 1 0
+--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+


The 32-bit value is put into that location.
But when it truncates it, it actually truncates bits [15:0] and leaves the pointer address as 0x100. This means when it returns, I end up seeing what was bits [31:16] thus all zeros.

That's my best guess.

Can someone explain this? Thanks :).

Answer

i2c_read() is receiving argument uint16_t *data which says "here is an address to 2 bytes of memory":

data --> [ByteA][ByteB]

When you perform the cast (uint32_t*)data, you're now claiming that data is an address to 4 bytes of memory:

data --> [ByteA][ByteB][ByteC][ByteD]

In good faith, low_level_read() uses the address that you passed as uint32_t* and populates all 4 bytes of memory as [00][00][C1][01]. This is bad. Pointer data has no rights to [ByteC][ByteD] and now you've overwritten memory that may have held some important data for some other part of your program.

Back in i2c_read() at the printf(), variable data goes back to being just a uint16_t* and *data reads just [ByteA][ByteB] as the value to print ([00][00]).
If you instead called printf("℅08X", *(uint32_t*)data), 4 bytes would be read and 0x00000C01 would print.

To fix your code, ensure that the argument types of i2c_read() and low_level_read() are the same.

Comments