Little vs Big Endian

Enable Javascript for TOC.

Data in Memory

The number (e.g. 0x12345678 for 305419896) is written to the memory, to disk (as binary file) or via network (as binary stream) either in Little Endian or Big Endian (Network Byte Order). When writing and reading the data on the same architecture, you get the correct number. When switching between systems you have to convert them (htonl, htons and ntohl, ntohs).
#include <endian.h>

#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	printf("Big endian\n");
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
	printf("Little endian\n");
#else
	#error Byte Order Error
#endif

int cnt;
unsigned int a = 0x12345678;
unsigned char b[16];
memset(b, 0, sizeof(b));
*(unsigned int*)&b[8] = a;
for (cnt=0; cnt < sizeof(b); cnt++) {
	printf(" %2d: 0x%02x\n", cnt, b[cnt]);
}
x86 PPC
Little endian
  0: 0x00
  1: 0x00
  2: 0x00
  3: 0x00
  4: 0x00
  5: 0x00
  6: 0x00
  7: 0x00
  8: 0x78
  9: 0x56
 10: 0x34
 11: 0x12
 12: 0x00
 13: 0x00
 14: 0x00
 15: 0x00
Big endian
  0: 0x00
  1: 0x00
  2: 0x00
  3: 0x00
  4: 0x00
  5: 0x00
  6: 0x00
  7: 0x00
  8: 0x12
  9: 0x34
 10: 0x56
 11: 0x78
 12: 0x00
 13: 0x00
 14: 0x00
 15: 0x00

Bitfields and the Endianness

In GCC the bitfields behave the same as bytes - this means their order reverse with switching the byte-order.
x86 (Little Endian) PPC (Big Endian)
struct test {
	unsigned int lsb  : 1;
	unsigned int cnt1 : 1;
	unsigned int cnt2 : 1;
	unsigned int cnt3 : 1;
	unsigned int cnt4 : 1;
	unsigned int cnt5 : 1;
	unsigned int cnt6 : 1;
	unsigned int msb  : 1;
};
struct test {
	unsigned int msb  : 1;
	unsigned int cnt6 : 1;
	unsigned int cnt5 : 1;
	unsigned int cnt4 : 1;
	unsigned int cnt3 : 1;
	unsigned int cnt2 : 1;
	unsigned int cnt1 : 1;
	unsigned int lsb  : 1;
};
The memory layout of structures is defined in the ABI of GCC - this means that each architecture can have a different layout (I guess for optimization).
Summary
If the data is always in system byte-order (e.g. because it's read/write only by systems with the same endianness) you have to decide:
  • if the data is only accessed over the bitset - so you don't have to care
  • if the data is accessed over the bitset and also via memory (uint32_t* pointer) you have to use the __BYTE_ORDER__ define and revert the sequence of the little-endian bitset. This also works, if the bitsets are spread over byte-borders... see example below.
If the data are always in the same byte-order (e.g. always in network byte-order, which is big endian) you can used either
  • scalar_storage_order (works only with GCC 6.0 or later) or
  • the __BYTE_ORDER__ define and revert the sequence of the little-endian bitset, but then there must be no bitset which goes over a byte-border!

With __BYTE_ORDER__ Define (works only with GCC)

During compile time the byte order can be checked by the __BYTE_ORDER__ define. The bitsfield must be reversed (like in the last example) but also the byte order must be considered:
#include <endian.h>

/** Data is big-endian! */
struct websocket_client_hdr {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	// big-endian data on a big-endian system
	unsigned int fin : 1;         ///< Indicates that this is the final fragment in a message
	unsigned int rsv : 3;         ///< Reserved
	unsigned int opc : 4;         ///< Opcode - see enum websocket_obcode
	unsigned int msk : 1;         ///< Mask (must be set to 1)
	unsigned int pl1 : 7;         ///< Payload length, 126=16bit, 127=64bit
#else
	// big-endian data on a little-endian system
	unsigned int opc : 4;         ///< Opcode - see enum websocket_obcode
	unsigned int rsv : 3;         ///< Reserved
	unsigned int fin : 1;         ///< Indicates that this is the final fragment in a message
	unsigned int pl1 : 7;         ///< Payload length, 126=16bit, 127=64bit
	unsigned int msk : 1;         ///< Mask (must be set to 1)
#endif
	char key[4];                  ///< Masking key
} __attribute__((__packed__));
In this example, on little-endian systems the byte-order is corrected. This only works, when no bitfield goes across a byte border.

With Scalar_Storage_Order Attribute (works only with >= GCC 6)

When the data is specified in a fix byte order - e.g. because it was received via network - it can be 'implemented' with the scalar_storage_order attribute. In this case, the compiler arranges it on all architectures (little endian as x86 and big endian as ppc) the same:
struct websocket_client_hdr {
	unsigned int fin : 1;         ///< Indicates that this is the final fragment in a message
	unsigned int rsv : 3;         ///< Reserved
	unsigned int opc : 4;         ///< Opcode - see enum websocket_obcode
	unsigned int msk : 1;         ///< Mask (must be set to 1)
	unsigned int pl1 : 7;         ///< Payload length, 126=16bit, 127=64bit
	char key[4];                  ///< Masking key
} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));
WARNING: When a bitset has exactly 16 bits (independent of its position in the struct) it is still in big-endian and must be accessed with ntohs()!
struct websocket_client_hdr {
	unsigned int spare : 7;
	unsigned int param1 : 4;
	unsigned int param2 : 16;         ///< stored in big-endian --> needs ntohs()
	unsigned int param3 : 5;
} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));
WARNING: When using anonymous structs or unions, these have to be marked with scalar_storage_order, e.g.:
struct base {
	u32 id;
	struct type_struct type;
	union {
		struct some_struct_one one;       ///< 8 byte size!
		struct {
			u32 one_u32;
			u32 RF_time;
		} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));
	} /* no direct members -> no attribute needed */;
	union {
		struct some_struct_two one;       ///< 4 byte size!
		u32 two_u32;
	} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));
	u32 spare;
} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));

With Scalar_Storage_Order #Pragma (works only with >= GCC 6)

The same as above, but now with #pragma instead of __attribute(()):
// define as network byte-order
#pragma scalar_storage_order big-endian
struct websocket_client_hdr {
	unsigned int fin : 1;         ///< Indicates that this is the final fragment in a message
	unsigned int rsv : 3;         ///< Reserved
	unsigned int opc : 4;         ///< Opcode - see enum websocket_obcode
	unsigned int msk : 1;         ///< Mask (must be set to 1)
	unsigned int pl1 : 7;         ///< Payload length, 126=16bit, 127=64bit
	char key[4];                  ///< Masking key
} __attribute__((__packed__));
#pragma scalar_storage_order default

Bitset Example

The following example shows the usage either on 'native data' and on 'big-endian data'
#include <stdio.h>

// taken from /usr/include/x86_64-linux-gnu/bits/byteswap.h
#define my_bswap_64(x)			\
    ((((x) & 0xff00000000000000ull) >> 56)	\
   | (((x) & 0x00ff000000000000ull) >> 40)	\
   | (((x) & 0x0000ff0000000000ull) >> 24)	\
   | (((x) & 0x000000ff00000000ull) >> 8)	\
   | (((x) & 0x00000000ff000000ull) << 8)	\
   | (((x) & 0x0000000000ff0000ull) << 24)	\
   | (((x) & 0x000000000000ff00ull) << 40)	\
   | (((x) & 0x00000000000000ffull) << 56))

/** Bitset without scalar_storage_order */
struct native_bitset {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	// big-endian data on a big-endian system
	unsigned int a : 8;
	unsigned int b : 12;
	unsigned int c : 4;
	unsigned int d : 8;

	unsigned int e : 8;
	unsigned int f : 14;
	unsigned int g : 2;
	unsigned int h : 8;
#else
	// little-endian data on a little-endian system
	unsigned int h : 8;
	unsigned int g : 2;
	unsigned int f : 14;
	unsigned int e : 8;

	unsigned int d : 8;
	unsigned int c : 4;
	unsigned int b : 12;
	unsigned int a : 8;
#endif
} __attribute__((__packed__));

/** Bitset with scalar_storage_order */
struct be_bitset {
	// big-endian data on all systems
	unsigned int a : 8;
	unsigned int b : 12;
	unsigned int c : 4;
	unsigned int d : 8;

	unsigned int e : 8;
	unsigned int f : 14;
	unsigned int g : 2;
	unsigned int h : 8;
} __attribute__((__packed__)) __attribute((scalar_storage_order("big-endian")));

int main() {
	unsigned long long int input_value = 0x123456789abcdefLLU;

	//
	// Native Byteorder
	//
	unsigned long long int value = input_value;
	struct native_bitset *value_bitset = (struct native_bitset*)&value;
	printf("   a=0x%02x\n", value_bitset->a);
	printf("   b=0x%02x\n", value_bitset->b);
	printf("   c=0x%02x\n", value_bitset->c);
	printf("   d=0x%02x\n", value_bitset->d);

	printf("   e=0x%02x\n", value_bitset->e);
	printf("   f=0x%02x\n", value_bitset->f);
	printf("   g=0x%02x\n", value_bitset->g);
	printf("   h=0x%02x\n", value_bitset->h);


	//
	// Fix Big-Endian Byteorder
	//
#if __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__
	unsigned long long int be_value = my_bswap_64(input_value);
#else
	unsigned long long int be_value = input_value;
#endif
	struct be_bitset *be_value_bitset = (struct be_bitset*)&be_value;
	printf("be_a=0x%02x\n", be_value_bitset->a);
	printf("be_b=0x%02x\n", be_value_bitset->b);
	printf("be_c=0x%02x\n", be_value_bitset->c);
	printf("be_d=0x%02x\n", be_value_bitset->d);

	printf("be_e=0x%02x\n", be_value_bitset->e);
	printf("be_f=0x%02x\n", be_value_bitset->f);
	printf("be_g=0x%02x\n", be_value_bitset->g);
	printf("be_h=0x%02x\n", be_value_bitset->h);

	return 0;
}
It produces the following output:
$ gcc -g -Wall main.c -o le && ./le
   a=0x01
   b=0x234
   c=0x05
   d=0x67
   e=0x89
   f=0x2af3
   g=0x01
   h=0xef
be_a=0x01
be_b=0x234
be_c=0x05
be_d=0x67
be_e=0x89
be_f=0x2af3
be_g=0x01
be_h=0xef
$
endianess test