Jul 2, 2020

Representation of big- and little- endians in memory

In previous posts, I've briefly discussed endianess. In this post I will look further into this topic and provide some C++ code. I'm using Windows 10 64 bit with Microsoft Visual Community C++2019.

When I'm building programs in my IDE, I'm building in x86 debug mode. Yes, x86 means little endianess. Little endianess means LSB byte of the data is stored at lower address.

Let's look into an C++ example, how to display the addresses and values.
uint32_t data = 0xDEADBEEF;
This is the value we are going investigate how it's represented in memory. The LSB byte is EF and since we are dealing with x86, it will be stored at lower address.

First we need a function to output the content of each memory address.
void DisplayMemory(uint32_t data)
{
  auto address = reinterpret_cast<uint8_t*>(&data);
  auto n = sizeof(data);

  for (std::size_t i = 0; i < n; i++) {
    std::cout << "[" 
              << static_cast<void*>(address + i) 
              << "]: " 
              << std::setw(2) 
              << std::setfill('0')
              << std::hex
              << static_cast<uint32_t>(*(address + i))
              << std::endl;
  }
}
Let's discuss the snippet above in more detail.
auto address = reinterpret_cast<uint8_t*>(&data);
Above, we cast the address of data from uint32_t pointer to uint8_t pointer and store it in an address pointer. We have to do this cast, so we can step the address pointer byte by byte. Since those types are unrelated, we have to use the reinterpret_cast operator (and not static_cast) for this purpose. We could use (uint8_t*) for conversion as well. Let's continue with the display part of the code.
std::cout << "[" 
          << static_cast<void*>(address + i) 
          << "]: " 
          << std::setw(2) 
          << std::setfill('0')
          << std::hex
          << static_cast<uint32_t>(*(address + i))
          << std::endl;
Above, we cast address + i, i.e. the uint8_t pointer to void pointer. Why? If we pass uint8_t pointer to the operator<<, it will handle the pointer as a string (char*). Since we want to display the address as a hexadecimal address, we must use the static_cast operator and cast to void pointer for this purpose. It is sufficient with the static_cast operator, since all types of pointers can be converted to void pointers (reinterpret_cast not needed).

There is one more static_cast above. It is needed to display the content of the address pointer. Like the previously cast, we need static_cast since we don't want to display a character, but a number. As a matter of fact, integer promotion could have solved this as well.
+(*(address + i))
The for-loop call std::cout for each address pointer and display the associated byte value.

Note that we could have used printf as well in this example.
printf("[%x]: %.2x\n", address + i, *(address + i));
Let's find out how the output from this code below, look like.
DisplayMemory(data);
 
0xDEADBEEF - Little endianess

Above, we see the little endianess representation of 0xDEADBEEF. LSB byte ef is stored at lower address.

Now, let's continue with big endianess. Big endianess means MSB byte of the data is stored at lower address. In our example, the MSB byte is DE, it will be stored at lower address, which we will se below.

First we need a function to convert from little endianess to big endianess.
uint32_t SwapEndians(uint32_t data)
{
   return (data >> 24) | 
           ((data & 0x00FF0000) >> 8) |
           ((data & 0x0000FF00) << 8) | 
           (data << 24);
}
The implementation above uses shift and binary manipulations. Another implementation would be to copy the bytes in wanted order.
uint32_t SwapEndians(uint32_t data)
{
  uint32_t result;
  uint8_t *resultPtr = reinterpret_cast(&result);
  uint8_t *dataPtr = reinterpret_cast(&data);
  resultPtr[0] = dataPtr[3];
  resultPtr[1] = dataPtr[2];
  resultPtr[2] = dataPtr[1];
  resultPtr[3] = dataPtr[0];
  
  return result;
}
Now, let's use this function to display the big endian representation of 0xDEADBEEF.
uint32_t data = 0xDEADBEEF;
auto b = SwapEndians(data);
DisplayMemory(b);

0xDEADBEEF - Big endianess

Above, we see the big endianess representation of 0xDEADBEEF. MSB byte be is stored at lower address.

You are welcome to leave comments, complaints or questions!