Endianness refers to the sequence in which a multi byte value is stored in memory.
A four byte int could have its least significant byte at the lowest address or its most significant byte at the lowest address, known as little endian and big endian, respectively. Different machines use different byte orders, so the same numerical value will be represented by different sequences of bytes depending on the platform.
The difference becomes apparent when you inspect the bytes of a known value. This program stores a specific int and then prints its four bytes as they're represented in the object's memory layout.
#include <iostream>
union U
{
unsigned int value;
unsigned char bytes[sizeof(unsigned int)];
};
int main()
{
U u;
u.value = 0x12345678;
std::cout << static_cast<int>(u.bytes[0]) << ' '
// Perform this conversion explicitly
<< static_cast<int>(u.bytes[1]) << ' '
<< static_cast<int>(u.bytes[2]) << ' '
<< static_cast<int>(u.bytes[3]) << '\n';
}
The union U contains an unsigned int named value and an array of unsigned char named bytes that's exactly the same size. Writing u.value = 0x12345678 stores the int in memory. Reading u.bytes[0] through u.bytes[3] will then access those bytes.
Little endian machines will produce 120 86 52 18, while big endian machines will print 18 52 86 120.
It's the same int, just in a different order.
On a little endian machine, the byte with value 0x78 resides at the lowest address, followed by the byte for 0x56 and the most significant byte 0x12 at the highest. Big endian reverses this, putting the most significant byte at the lowest address and the least significant byte last.
This same convention applies to every multi byte data type, whether it is a short two byte value, an eight byte long long, or a floating point value. Single byte types such as char are not affected as there is only a single byte, so there's no order to consider.
Endianness becomes a practical concern once data is exchanged between machines, or written out to a file for that matter. A little endian machine might write an int out to disk, only to have that four byte sequence later interpreted by a big endian machine and translated into a very different number. For instance, if we transmit bytes 0x78 0x56 0x34 0x12 to a big endian machine, it will read them in as 0x78563412 rather than the original 0x12345678. That's not any kind of undefined behavior; that's just the wrong number.
One way to check the machine's endianness is to write a known value and see where its first byte is stored.
#include <iostream>
union U
{
unsigned int value;
// Query the size in bytes without evaluating the operand
unsigned char bytes[sizeof(unsigned int)];
};
int main()
{
U u;
u.value = 1;
if (u.bytes[0] == 1)
std::cout << "little-endian\n";
else
std::cout << "big-endian\n";
}
The int 1 can be thought of as having a 1 only in its least significant bit. After the assignment u.value = 1, the array element u.bytes[0] will hold 1 on a little endian machine, and 0 on a big endian machine. The if statement thus reports the correct endianness.
Since the two byte orders are simple mirrors of each other, converting a value from one endianness to the other simply involves reversing the order of its bytes. The language itself does not provide a standard function to perform this translation; it must be coded by the programmer. When such a translation is left out and data crosses an endianness boundary, a different numeric value appears; no undefined behavior ensues, but the value is just wrong.
0 comments
No comments yet. Be the first.