Big Endian vs Little Endian: Understanding Byte Order

Understanding Big and Little Endian Byte Order

Endianness in one minute

A 32-bit integer such as 0x12345678 is one number, but memory, flash, a file or a serial link can only hold it as four separate bytes. Endianness is the rule that decides which of those four bytes comes first.

  • Big endian puts the most significant byte (0x12) at the lowest address, or first on the wire.
  • Little endian puts the least significant byte (0x78) at the lowest address, or first on the wire.
Byte offset +0 +1 +2 +3
Big endian 12 34 56 78
Little endian 78 56 34 12

Three things the term does not cover are worth fixing in your head early. It says nothing about the order of the eight bits inside a byte; that is a property of a physical link, discussed below. It does not affect a single byte: 0xA5 is stored as 0xA5 everywhere. And it does not apply to a plain byte string such as an ASCII or UTF-8 message, which is already a sequence of single bytes. Endianness only matters when a value wider than one byte has to be turned into bytes, or rebuilt from them.

Big endian vs little endian at a glance

Property Big endian Little endian
Byte at the lowest address Most significant Least significant
Byte at the highest address Least significant Most significant
0x12345678 in memory 12 34 56 78 78 56 34 12
Hex dump readability Multi-byte values read left to right as written Multi-byte values appear reversed
Same value at different widths Low-order byte moves as the width changes Low-order byte stays at the start address
Typical contexts IP, TCP and UDP headers; Modbus; PNG; SHA-2 message words; smart-card length fields x86 and x86-64 memory; mainstream Arm operating systems; USB descriptor fields; ChaCha20

Neither order is faster in any general sense. Little endian lets a program read the low 8 or 16 bits of a wider integer from the same address, which suits multi-precision arithmetic that walks from the least significant end. Big endian keeps hex dumps and protocol traces readable, and it makes byte-wise comparison of unsigned keys agree with numeric comparison. Most of the trouble comes not from either choice but from two systems that disagree, or from code that never states which one it expects.

A third family, sometimes called middle- or mixed-endian, existed on older machines such as the PDP-11, which stored a 32-bit value as two 16-bit halves in an order that matched neither scheme. You will rarely meet it in new silicon, but GCC still defines __ORDER_PDP_ENDIAN__ for it, and the same “word swap” problem reappears in Modbus devices, covered later.

How to find out which byte order you have

There are three different questions hiding behind “what endianness is this?”, and each has a different answer.

  1. What does this CPU use for its own memory? This is fixed by the processor (or by its configuration at reset) and the toolchain knows it at compile time.
  2. What does this protocol or file format use? This is fixed by the specification of the data, and it does not change with the machine that happens to read it.
  3. What did this particular sender actually do? Only a format with a byte-order marker or a metadata field can tell you at run time.

For the first question, the compiler normally already has the answer. GCC and Clang predefine __BYTE_ORDER__, to be compared with __ORDER_LITTLE_ENDIAN__ or __ORDER_BIG_ENDIAN__. C23 standardizes the same idea in <stdbit.h> as __STDC_ENDIAN_NATIVE__, and C++20 offers std::endian::native in <bit>.

If you need a run-time check, for a self-test or a diagnostic log line, copy the first byte of a known value into an unsigned char. Reading an object’s bytes through a character type is always permitted in C, so this version has no undefined behavior and no alignment assumptions:

#include <stdint.h>
#include <string.h>

/* Returns 1 on a little-endian host, 0 on a big-endian host. */
static int host_is_little_endian(void)
{
    const uint16_t probe = 0x0102;
    unsigned char first;

    memcpy(&first, &probe, 1);   /* the byte at the lowest address */
    return first == 0x02;
}

The more useful lesson is that well-written code rarely needs to ask. If every external field is decoded with explicit shifts, as shown in the parsing example below, the same source produces the right answer on any host, and the host’s byte order stops being a variable.

x86 and x86-64 byte order

x86 and x86-64 processors are little endian. Intel’s Software Developer’s Manual describes Intel 64 and IA-32 processors as little-endian machines: the bytes of a word are numbered from the least significant byte, so a 32-bit store of 0x12345678 to address A leaves 78 at A, 56 at A+1, 34 at A+2 and 12 at A+3.

What x86 does provide is fast conversion. BSWAP reverses the bytes of a 32- or 64-bit register, and MOVBE, on processors that report it through CPUID, loads or stores a value with its bytes reversed in a single instruction. You seldom need to write either by hand. When we compiled the portable load_be32() function shown later with Clang at -O2, the x86-64 output was one 32-bit load followed by bswap, and the 64-bit Arm output was one load followed by rev, Arm’s byte-reverse instruction.

The CPU’s byte order also does not decide the byte order of data it handles. An x86 server still has to put TCP port numbers on the wire in big-endian order, still has to read big-endian length fields from a smart card, and still has to honor whatever a sensor’s register map says. “My machine is little endian” answers question 1 above and none of the others.

Arm and endianness

Arm is the case where a one-word answer is wrong, because the architecture separates what a core can do from how it is configured.

  • A-profile (Armv8-A, Armv9-A). In AArch64 the endianness of data accesses (and of translation-table walks) is set per exception level in the system control registers (SCTLR_ELx.EE, plus SCTLR_EL1.E0E for applications). Whether a core supports big-endian data at all, and whether the setting can be changed, is implementation defined and is reported in ID_AA64MMFR0_EL1. A64 instructions themselves are always little endian.
  • M-profile (Cortex-M). The byte order is fixed for the device, either in the design or by a hardware input sampled at reset, and software cannot change it. It can be read from the ENDIANNESS bit of the Application Interrupt and Reset Control Register (AIRCR). The core’s own system registers in the Private Peripheral Bus region are little endian even on a big-endian device.
  • What developers actually meet. The mainstream platforms run Arm in little-endian mode. Microsoft documents that Windows on ARM64 executes in little-endian mode, Android documents that it is always little endian, and Apple documents that Apple silicon Macs use the little-endian format. For a microcontroller, take the answer from the device reference manual or from AIRCR, not from the word “Arm”.

So the accurate short answer to “is ARM big endian or little endian?” is: Arm cores can support either, the configuration is chosen by the system or the silicon vendor, and the systems most developers work with are configured little endian.

Network byte order

Two machines exchanging binary data need to agree on how a 16- or 32-bit field is laid out, otherwise a length of 300 sent by one is read as 11,265 by the other. Internet protocols settle this once for everyone. The IP specification, RFC 791, defines the order of transmission of its header fields with the most significant octet first, and the same convention runs through TCP, UDP and most IETF protocols that carry binary fields. That convention, big endian, is what “network byte order” means. The host’s own convention is called host byte order.

The sockets API gives you four conversion functions, declared in <arpa/inet.h>:

Function Converts
htons() 16-bit value, host to network order
htonl() 32-bit value, host to network order
ntohs() 16-bit value, network to host order
ntohl() 32-bit value, network to host order

On a big-endian host there is nothing to convert, so they return their argument unchanged; on a little-endian host they reverse the bytes. Some C libraries implement them as macros. A familiar embedded example is opening a Modbus TCP socket on port 502: addr.sin_port = htons(502); places the bytes 01 F6 in the structure, which is the big-endian encoding the network stack expects. For 64-bit fields and for little-endian protocols, POSIX.1-2024 standardizes <endian.h> with htobe64(), be32toh(), htole16() and related functions, which glibc and the BSDs have provided for many years.

Network byte order is a convention of the IP family, not a law of networking. USB, for example, defines its multi-byte fields as little endian. The protocol specification is always the authority.

The names come from the egg-breaking factions in Gulliver’s Travels. Danny Cohen’s 1980 note On Holy Wars and a Plea for Peace (IEN 137) applied them to byte order and argued that agreeing on one order mattered more than which order was chosen.

Byte order, bit order and wire order are three different things

This is the distinction that causes the most confusion in embedded work, because all three show up in the same logic-analyzer trace.

  • Byte order: how a multi-byte value is split into a sequence of bytes. This is endianness.
  • Bit order: the order in which a serial link sends the bits inside each byte, least significant bit (LSB) first or most significant bit (MSB) first.
  • Wire order: the order in which a protocol’s fields, and therefore bytes, appear in a frame.

A serial bus defines only the second item. It has no opinion about which half of a 16-bit sensor reading you send first.

Interface Bit order within a byte Who defines multi-byte order
UART (asynchronous serial) LSB first after the start bit The protocol carried over it (Modbus RTU registers are big endian, for example)
I²C MSB first Each device’s register map
SPI A controller setting: MSB first is the common choice, and many controllers can also shift LSB first Each device’s command and data format
USB LSB first (CRC fields excepted) The USB specification: multi-byte fields are little endian

So the questions “does SPI have an endianness?” and “does UART have an endianness?” both have the answer no. They have a bit order. The byte order belongs to whatever you put on top of them, and it has to be read from that device’s datasheet or that protocol’s specification.

Where endianness bites in embedded systems

Sensor registers

Two widely used I²C sensors make the point on their own. ST’s LIS3DH accelerometer places each axis reading low byte first, OUT_X_L at register 0x28 and OUT_X_H at 0x29, and offers a configuration bit that swaps the order in its high-resolution mode. Bosch’s BMP280 pressure sensor does the opposite for its temperature result, with the most significant byte at 0xFA followed by the less significant bytes at 0xFB and 0xFC. A burst read returns bytes in register-address order in both cases, so the same driver code cannot assemble both values correctly. The register map, not the bus and not the MCU, decides.

Modbus and other field protocols

The Modbus application protocol is explicitly big endian for addresses and data, and its registers are 16 bits wide. A 32-bit integer or an IEEE 754 float therefore has to span two registers, and the base specification does not say which register holds the high half. Meters and drives differ, which is why integration tools offer “word swap” and “byte swap” options. Treat the register order as part of each device’s documentation and test it with a known reading.

Firmware images, bootloaders and stored data

A bootloader that checks an image header reads a length, a version and a CRC that were written by a build tool, usually on a different machine. The ELF format solves this by recording the encoding in its identification bytes (EI_DATA is either ELFDATA2LSB or ELFDATA2MSB), and your own image headers should be just as explicit. The same applies to calibration data, configuration records and event logs kept in EEPROM or flash: once a field outlives the firmware that wrote it, its byte order must be written down.

Cryptography and security protocols

Algorithms specify their own byte order and ignore the CPU’s. SHA-256, as defined in NIST FIPS 180-4, treats its message words as big endian, while ChaCha20 and Poly1305, as defined in RFC 8439, read their words as little endian. A hash that is correct on a desktop and wrong on a microcontroller, or the reverse, is very often a missing or extra byte swap in exactly this step.

Smart cards and payment data

The multi-byte values that smart-card engineers meet most often are big endian. BER-TLV, the encoding used for card data objects, writes a long-form length as an unsigned integer whose first byte is the most significant (ITU-T X.690), so 82 01 2C announces a value of 300 bytes. An extended-length command APDU carries Lc as 00 followed by a two-byte length. On the card itself, Java Card’s Util.makeShort(b1, b2) takes b1 as the high-order byte. Ambimat’s notes on BER-TLV encoding in ISO/IEC 7816-4 and on APDU message structure show these fields in context.

Worked example: decoding a four-byte field

Suppose a device sends the four bytes 12 34 56 78 for a 32-bit counter.

Interpreted as Hex Decimal
Big endian 0x12345678 305,419,896
Little endian 0x78563412 2,018,915,346

Both are plausible counter values, which is why this class of bug survives casual testing. The robust way to decode is to build the value from individual bytes with shifts. The code below works on any host, never reads through a misaligned pointer, and states the byte order in the function name:

#include <stdint.h>

uint32_t load_be32(const uint8_t *p)
{
    return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
           ((uint32_t)p[2] << 8)  |  (uint32_t)p[3];
}

uint32_t load_le32(const uint8_t *p)
{
    return  (uint32_t)p[0]        | ((uint32_t)p[1] << 8) |
           ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}

void store_be32(uint8_t *p, uint32_t v)
{
    p[0] = (uint8_t)(v >> 24);
    p[1] = (uint8_t)(v >> 16);
    p[2] = (uint8_t)(v >> 8);
    p[3] = (uint8_t)v;
}

Two details matter. The cast to uint32_t comes before the shift: a uint8_t is promoted to a signed int, and shifting 0x80 left by 24 overflows a 32-bit int, which is undefined behavior in C. And signed fields need an explicit step after assembly. For a 16-bit two’s-complement reading, (raw & 0x8000u) ? (int32_t)raw - 0x10000 : (int32_t)raw gives the right sign on every compiler; the bytes FF 38 big endian, or 38 FF little endian, both decode to −200.

Endianness in binary file formats

File formats handle byte order in one of three ways.

  • One fixed order. PNG stores every multi-byte integer in network byte order, so a PNG decoder never has to guess.
  • A byte-order marker. A TIFF file begins with II for little endian or MM for big endian. UTF-16 text may begin with the byte-order mark U+FEFF, which appears as FE FF in big-endian UTF-16 and FF FE in little-endian UTF-16. A reader that also accepts UTF-32 must look further, because the UTF-32LE mark is FF FE 00 00.
  • A metadata field. ELF’s EI_DATA byte records the encoding of everything that follows, which lets one toolchain produce images for targets of either order.

For a new format, a single fixed order is the simplest to implement and to test. A marker is only worth having if writers on different architectures must avoid a conversion cost.

Common endian bugs

  1. Casting a receive buffer to an integer pointer. *(uint32_t *)&buf[3] silently uses host byte order, may fault on a core that rejects unaligned access, and breaks C’s aliasing rules. Use explicit loads instead.
  2. Assuming both ends match. An MCU and a PC tool that are both little endian will agree by coincidence until one side changes, for example when the PC side is replaced by a big-endian protocol gateway or a Java tool.
  3. Writing C structs directly to the wire or to flash. Byte order, padding, field alignment and enum sizes all depend on the compiler and target.
  4. Confusing byte order with bit order. Setting an SPI controller to LSB-first does not swap the bytes of a 16-bit value, and swapping bytes does not fix a bit-order mismatch.
  5. Swapping twice. A value converted with htonl() in one layer and again in another arrives in host order on the wire. Convert once, at the boundary.
  6. Undocumented field order. A protocol document that lists field widths but not their byte order guarantees a future integration bug.
  7. Ignoring signedness. A negative temperature assembled into an unsigned variable becomes a reading near 65,000.

Designing an endian-safe embedded protocol

When you define the frame format for a new device, firmware update or host interface, these rules remove almost every byte-order defect before it is written:

  1. State the byte order once, in the first paragraph of the specification, and keep it the same for every field.
  2. Give every field an offset, a width, a signedness and a unit, so that nobody has to infer them from sample captures.
  3. Encode and decode with explicit load and store functions like the ones above, or with a code generator, never with raw structs.
  4. Publish known test vectors: a sample frame as bytes, with the decoded value of every field.
  5. Test on more than one architecture where practical, for example the MCU target, an x86 host and a host-side unit test built with an undefined-behavior sanitizer.
  6. Version the format, so that a later change of field layout is visible rather than silent.

A small example of rule 2:

Field Offset Type
Frame type 0 uint8
Sequence number 1 uint16, big endian
Temperature, 0.01 °C 3 int16, big endian, two’s complement
Uptime, seconds 5 uint32, big endian
CRC-16 over bytes 0–8 9 uint16, big endian

If you are planning a device of your own, our embedded firmware development page describes how we approach firmware projects.

Frequently asked questions

Is x86 little endian?

Yes. Intel 64 and IA-32 processors store multi-byte values with the least significant byte at the lowest address. Data formats that the x86 machine reads or writes can still be big endian.

Is ARM big endian or little endian?

Either, depending on the core and its configuration. AArch64 can support big-endian data per exception level where the implementation allows it, and a Cortex-M device has its order fixed in hardware. Windows, Android and macOS all run Arm little endian; for a microcontroller, check the reference manual or AIRCR.

What is network byte order?

Big endian: the most significant byte of a field is sent first. It is the convention defined for the IP protocol family, and the reason the sockets API provides htons(), htonl(), ntohs() and ntohl().

Why is network byte order big endian?

Because the early Internet specifications defined it that way, and the protocols built on them kept it. What matters is that every implementation agrees on one order; the choice of big endian is a convention rather than a technical advantage.

Does endianness affect one-byte values or text?

Not single bytes, and not byte-oriented text such as ASCII or UTF-8. It does affect UTF-16 and UTF-32, which is why those encodings can carry a byte-order mark.

Is byte order the same as bit order?

No. Byte order describes how a multi-byte value is laid out in memory or in a message. Bit order describes how a serial link transmits the bits within each byte. They are set independently.

How do I convert between big and little endian?

For data crossing a boundary, decode and encode with explicit shift-based functions such as load_be32() and store_be32(). For socket fields, use htons() and its relatives. Compilers also offer byte-swap built-ins such as GCC’s __builtin_bswap32(), and C++23 adds std::byteswap.

Does SPI or UART have an endianness?

No. They define bit order within a byte (UART sends LSB first; SPI bit order is a controller setting, commonly MSB first). The order of the bytes in a multi-byte value is set by the device or protocol that uses the link.

How can I tell my machine’s endianness?

At compile time, test __BYTE_ORDER__ (GCC and Clang), __STDC_ENDIAN_NATIVE__ (C23) or std::endian::native (C++20). At run time, copy the first byte of a known 16-bit value into an unsigned char, as in the function shown earlier.

Primary sources

This article is an independent engineering reference written by Ambimat. It describes the cited specifications in our own words and is not affiliated with or endorsed by their publishers.