This answer is based on the following specs (this is for clarity):
Language: C++ v17, 64-Bit
Compilers: g++ v8 (The GNU Compiler Collection https://www.gnu.org/software/gcc/) & MingW 8.1.0 toolchain (https://sourceforge.net/projects/mingw-w64/files/)
OS's: Linux Mint & Windows
The following two lines of code can be used to successfully detect a processor's endianness:
const uint8_t IsLittleEndian = char (0x0001);
or
#define IsLittleEndian char (0x0001)
These two little magical statement gems take advantage of how the processor stores a 16-Bit value in memory.
On a "Little Endian" processor, like the Intel and AMD chipsets, a 16-Bit value is stored in a [low order/least significant byte][high order/most significant byte]
fashion (the brackets represent a byte in memory).
On a "Big Endian" processor, like the PowerPC, Sun Sparc, and IBM S/390 chipsets, a 16-Bit value is stored in a [high order/most significant byte][low order/least significant byte]
fashion.
For example, when we store a 16-Bit (two byte) value, let's say 0x1234
, into a C++ uint16_t
(type defined in C++ v11, and later https://en.cppreference.com/w/cpp/types/integer) size variable on a "Little Endian" processor, then peer into the memory block the value is stored in, you will find the byte sequence, [34][12]
.
On a "Big Endian processor", the 0x1234
value is stored as [12][34]
.
Here is a little demo to help demonstrate how various size C++ integer variables are stored in memory on little and big endian processors:
#define __STDC_FORMAT_MACROS // Required for the MingW toolchain
#include <iostream>
#include <inttypes.h>
const uint8_t IsLittleEndian = char (0x0001);
//#define IsLittleEndian char (0x0001)
std::string CurrentEndianMsg;
std::string OppositeEndianMsg;
template <typename IntegerType>
void PrintIntegerDetails(IntegerType IntegerValue)
{
uint16_t SizeOfIntegerValue = sizeof(IntegerValue);
int8_t i;
std::cout << "Integer size (in bytes): " << SizeOfIntegerValue << "\n";
std::cout << "Integer value (Decimal): " << IntegerValue << "\n";
std::cout << "Integer value (Hexidecimal): ";
switch (SizeOfIntegerValue)
{
case 2: printf("0x%04X\n", (unsigned int) IntegerValue);
break;
case 4: printf("0x%08X\n", (unsigned int) IntegerValue);
break;
case 8: printf("0x%016" PRIX64 "\n", (uint64_t) IntegerValue);
break;
}
std::cout << "Integer stored in memory in byte order:\n";
std::cout << " " << CurrentEndianMsg << " processor [current]: ";
for(i = 0; i < SizeOfIntegerValue; i++)https://stackoverflow.com/qhttps://mcmap.net/q/18624/-is-there-a-way-to-do-a-c-style-compile-time-assertion-to-determine-machine-39-s-endianness/54175491#54175491uestions/280162/is-there-a-way-to-do-a-c-style-compile-time-assertion-to-determine-machines-e/54175491#54175491
{
printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
}
std::cout << "\n " << OppositeEndianMsg << " processor [simulated]: ";
for(i = SizeOfIntegerValue - 1; i >= 0; i--)
{
printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
}
std::cout << "\n\n";
}
int main()
{
uint16_t ValueUInt16a = 0x0001;
uint16_t ValueUInt16b = 0x1234;
uint32_t ValueUInt32a = 0x00000001;
uint32_t ValueUInt32b = 0x12345678;
uint64_t ValueUInt64a = 0x0000000000000001;
uint64_t ValueUInt64b = 0x123456789ABCDEF0;
std::cout << "Current processor endianness: ";
switch (IsLittleEndian) {
case 0: CurrentEndianMsg = "Big Endian";
OppositeEndianMsg = "Little Endian";
break;
case 1: CurrentEndianMsg = "Little Endian";
OppositeEndianMsg = "Big Endian";
break;
}
std::cout << CurrentEndianMsg << "\n\n";
PrintIntegerDetails(ValueUInt16a);
PrintIntegerDetails(ValueUInt16b);
PrintIntegerDetails(ValueUInt32a);
PrintIntegerDetails(ValueUInt32b);
PrintIntegerDetails(ValueUInt64a);
PrintIntegerDetails(ValueUInt64b);
return 0;
}
Here is the output of the demo on my machine:
Current processor endianness: Little Endian
Integer size (in bytes): 2
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x0001
Integer stored in memory in byte order:
Little Endian processor [current]: 01 00
Big Endian processor [simulated]: 00 01
Integer size (in bytes): 2
Integer value (Decinal): 4660
Integer value (Hexidecimal): 0x1234
Integer stored in memory in byte order:
Little Endian processor [current]: 34 12
Big Endian processor [simulated]: 12 34
Integer size (in bytes): 4
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x00000001
Integer stored in memory in byte order:
Little Endian processor [current]: 01 00 00 00
Big Endian processor [simulated]: 00 00 00 01
Integer size (in bytes): 4
Integer value (Decinal): 305419896
Integer value (Hexidecimal): 0x12345678
Integer stored in memory in byte order:
Little Endian processor [current]: 78 56 34 12
Big Endian processor [simulated]: 12 34 56 78
Integer size (in bytes): 8
Integer value (Decinal): 1
Integer value (Hexidecimal): 0x0000000000000001
Integer stored in memory in byte order:
Little Endian processor [current]: 01 00 00 00 00 00 00 00
Big Endian processor [simulated]: 00 00 00 00 00 00 00 01
Integer size (in bytes): 8
Integer value (Decinal): 13117684467463790320
Integer value (Hexidecimal): 0x123456789ABCDEF0While the process
Integer stored in memory in byte order:
Little Endian processor [current]: F0 DE BC 9A 78 56 34 12
Big Endian processor [simulated]: 12 34 56 78 9A BC DE F0
I wrote this demo with the GNU C++ toolchain in Linux Mint and don't have the means to test in other flavors of C++ like Visual Studio or the MingW toolchain, so I do not know what is required for this to compile in them, nor do I have access to Windows at the moment.
However, a friend of mine tested the code with MingW, 64-Bit (x86_64-8.1.0-release-win32-seh-rt_v6-rev0) and it had errors. After a little bit of research, I discovered I needed to add the line #define __STDC_FORMAT_MACROS
at the top of the code for it to compile with MingW.
Now that we can visually see how a 16-Bit value is stored in memory, let's see how we can use that to our advantage to determine the endianness of a processor.
To give a little extra help in visualizing the way that 16-Bit values are stored in memory, let's look at the following chart:
16-Bit Value (Hex): 0x1234
Memory Offset: [00] [01]
---------
Memory Byte Values: [34] [12] <Little Endian>
[12] [34] <Big Endian>
================================================
16-Bit Value (Hex): 0x0001
Memory Offset: [00] [01]
---------
Memory Byte Values: [01] [00] <Little Endian>
[00] [01] <Big Endian>
When we convert the 16-Bit value 0x0001
to a char (8-Bit) with the snippet char (0x0001)
, the compiler uses the first memory offset of the 16-Bit value for the new value. Here is another chart that shows what happens on both the "Little Endian" and "Big Endian" processors:
Original 16-Bit Value: 0x0001
Stored in memory as: [01][00] <-- Little Endian
[00][01] <-- Big Endian
Truncate to char: [01][xx] <-- Little Endian
[01] Final Result
[00][xx] <-- Big Endian
[00] Final Result
As you can see, we can easily determine the endianness of a processor.
UPDATE:
I am unable to test the demo above on a "Big Endian" processor, so I based the code on information I found on the web. Thanks to M.M for pointing out the obvious to me.
I have updated the demo code (as seen below) to test for endianness or a processor correctly.
#define __STDC_FORMAT_MACROS // Required for the MingW toolchain
#include <iostream>
#include <inttypes.h>
std::string CurrentEndianMsg;
std::string OppositeEndianMsg;
template <typename IntegerType>
void PrintIntegerDetails(IntegerType IntegerValue)
{
uint16_t SizeOfIntegerValue = sizeof(IntegerValue);
int8_t i;
std::cout << "Integer size (in bytes): " << SizeOfIntegerValue << "\n";
std::cout << "Integer value (Decimal): " << IntegerValue << "\n";
std::cout << "Integer value (Hexidecimal): ";
switch (SizeOfIntegerValue)
{
case 2: printf("0x%04X\n", (unsigned int) IntegerValue);
break;
case 4: printf("0x%08X\n", (unsigned int) IntegerValue);
break;
case 8: printf("0x%016" PRIX64 "\n", (uint64_t) IntegerValue);
break;
}
std::cout << "Integer stored in memory in byte order:\n";
std::cout << " " << CurrentEndianMsg << " processor [current]: ";
for(i = 0; i < SizeOfIntegerValue; i++)
{
printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
}
std::cout << "\n " << OppositeEndianMsg << " processor [simulated]: ";
for(i = SizeOfIntegerValue - 1; i >= 0; i--)
{
printf("%02X ", (((unsigned char*) &IntegerValue)[i]));
}
std::cout << "\n\n";
}
int main()
{
uint16_t ValueUInt16a = 0x0001;
uint16_t ValueUInt16b = 0x1234;
uint32_t ValueUInt32a = 0x00000001;
uint32_t ValueUInt32b = 0x12345678;
uint64_t ValueUInt64a = 0x0000000000000001;
uint64_t ValueUInt64b = 0x123456789ABCDEF0;
uint16_t EndianTestValue = 0x0001;
uint8_t IsLittleEndian = ((unsigned char*) &EndianTestValue)[0];
std::cout << "Current processor endianness: ";
switch (IsLittleEndian) {
case 0: CurrentEndianMsg = "Big Endian";
OppositeEndianMsg = "Little Endian";
break;
case 1: CurrentEndianMsg = "Little Endian";
OppositeEndianMsg = "Big Endian";
break;
}
std::cout << CurrentEndianMsg << "\n\n";
PrintIntegerDetails(ValueUInt16a);
PrintIntegerDetails(ValueUInt16b);
PrintIntegerDetails(ValueUInt32a);
PrintIntegerDetails(ValueUInt32b);
PrintIntegerDetails(ValueUInt64a);
PrintIntegerDetails(ValueUInt64b);
return 0;
}
This updated demo creates a 16-Bit value 0x0001
and then reads the first byte in the variables memory. As seen in the output shown above, on the "Little Endian" processors, the value would be 0x01. On "Big Endian" processors, the value would be 0x00.