Saturday, May 4, 2019

Typecasting Pointers to different Datatypes - A Reflection


There were questions from multiple persons in the earlier article, all were about pointer typecasting and dereferencing. So I thought of writing about the same, let’s get started. Before that if you haven`t checked out those articles, please do so, the link is at the end of this article. It`s better to read this first before going through those links if you`re not familiar with pointer typecasting.


The most asked question is how this below code snippet ( and similar ) works?

uint32 u32Destination;
uint8 u8Source[ 12 ] ;
u8Source[ 0 ] = 0x01 ;
u8Source[ 1 ] = 0x23 ;
u8Source[ 2 ] = 0x45 ;
u8Source[ 3 ] = 0x67 ;


As you may know, I used this statement to copy the four byte value from long integer to the byte array contiguously. We can consider pointers which point to variables of different datatype to get to know how this works.


Before that let me start from basics, as you would have known pointer is just a ( integer ) variable which is used to hold the address of any other variable. To be precise it will have the address of the variable which it points to.


Let’s see what that means with the example. Consider the below code snippet,

uint32_t u32Integer = 0xAABBCC;
uint32_t * p32Pointer = & u32Integer.


Let’s assume the long integer variable u32Integer is located at 0x20000, then the pointer p32Pointer will be assigned the value of 0x20000 ( the address of u32Integer ). If you dereference the p32Pointer,  the controller will read four consecutive bytes from the location 0x20000 and will combine those four one byte values based on endianness of the architecture and will give you the four byte long integer value. So the statement ( * p32Pointer ) will give you the value of 0xAABBCC.


Let`s look at another example with a pointer to a short integer as shown below,

uint16_t u16Integer = 0xDDEE;
uint16_t * p16Pointer = & u16Integer.


For the sake of simplicity assume this u16Integer variable is also located in 0x20000, then the pointer p16Pointer will have the value of 0x20000 as well. In this case, if you dereference the p16Pointer then it will read two consecutive bytes from the location 0x20000 and will combine those two one byte values based on endianness of the architecture and will give you the two byte short integer value.


Note that there is no difference in the value ( address ) stored in the p16Pointer ( which is a pointer to a short integer ) and p32Pointer ( which is a pointer to a long integer ), both are having the value of 0x20000 which is the address of the variables u16Integer and u32Integer.


So during dereferencing, how the controller gives you the four byte value for p32Pointer and two byte value for p16Pointer? The difference is in the compiler interpretation while dereferencing the pointers. If the pointer points to the short integer then the compiler reads two bytes from the address the pointer points to. If the pointer points to the long integer then the compiler reads four bytes from the address the pointer points to.


So If you want to extract the lower two bytes from u32Integer and store it in uint16_t variable, you can achieve that with conventional AND method or typecasting, but you can also achieve the same using pointer as shown below,

uint16_t u16Integer = *( ( uint16_t * )  p32Pointer )


Have you got how this works? As we saw earlier, there is no difference in the value stored in the pointer variable, the pointer p32Pointer will have the value of 0x20000. By default, the compiler will interpret the p32Pointer as a pointer to the uint32_t as that`s what the datatype used in the declaration of p32Pointer. So if you want to read just two bytes from 0x20000, then you have to make that pointer as a pointer to uint16_t, this can be done through typecasting. So prefixing p32Pointer with ( uint16_t * ) will tell the compiler that the p32Pointer is a pointer to uint16_t, then the compiler will interpret the p32Pointer as a pointer to uint16_t.


Now if we dereference it, it will read two one byte values from the location 0x20000 and will combine those two bytes to give you the two byte short integer value. So as a result, u16Integer will have the value of  0xBBCC. Here you`ve to consider another subtlety, this result will come only in little endian based system, and will be different in big endian systems.


As I mentioned earlier, the value will be stored in memory based on the endianness of the system. For the example above, for the little endian based system, the value will be stored in memory as shown below,

0x20000 = 0xCC
0x20001 = 0xBB
0x20002 = 0xAA
0x20003 = 0x00


So in this case, if we read two bytes from 0x20000, the result we get is 0xCC and 0xBB, if we concatenate these two values based on little endian format the resultant two byte value is 0xBBCC.

So that`s what will be the value of *( ( uint16_t * )  p32Pointer ) statement.


Now let’s assume the endianness of the system architecture is big endian, the value will be stored in memory as shown below,

0x20000 = 0x00
0x20001 = 0xAA
0x20002 = 0xBB
0x20003 = 0xCC


So in this case, if we read two bytes from 0x20000, the result we get is 0x00 and 0xAA, if we concatenate these two values based on little endian format the resultant two byte value is 0x00AA.

So that`s what will be the value of *( ( uint16_t * )  p32Pointer ) statement.


I hope you understood how it works. Now let`s go back to the main question,

u32Destination  = *(( uint32_t * ) ( & u8Source [ 0 ] ) );


Here ( &u8Source[ 0 ] ) gives the address of the first element of u8Destination array. Let`s suppose it is located at 0x30000. Prefixing this ( &u8Source[ 0 ] ) with ( uint32_t * ) makes it the pointer to the unsigned long integer. So if you dereference it, the controller will read four one byte values starting from the location 0x30000 and will combine those values into one four byte value, which will be stored in u32Destination.


 So If you are working with little endian system and the buffer data is also aligned in little endian format, then you can use the dereference method discussed in my previous article. In big endian systems, your buffer data has to be aligned in the big endian format for this dereference method to work.


Refer the below link to know about the usage of dereferencing method,



No comments:

Post a Comment