Published on

Big Endian & Little Endian

Authors
  • Name
    ToanNV
    Twitter

Big Endian & Little Endian

Có những lúc gửi cùng một con số, nhưng bên nhận lại hiểu thành giá trị khác. Nguyên nhân thường nằm ở một chi tiết rất nhỏ mà cực kỳ quan trọng: byte ordering hay còn gọi là Endianness — thứ tự lưu byte trong bộ nhớ.


Register là gì?

Trước khi nói về Big Endian hay Little Endian, cần hiểu rõ register.

Tùy ngữ cảnh, từ "register" có thể mang nhiều nghĩa:

  • Trong CPU: register là vùng nhớ rất nhỏ, rất nhanh bên trong bộ xử lý.
  • Trong giao tiếp dữ liệu hoặc tài liệu thiết bị: register thường là một ô dữ liệu có kích thước cố định (thường gặp là 16-bit).

Trong phạm vi bài viết này, "register" được hiểu theo cách dùng phổ biến trong các giao thức công nghiệp như Modbus: một ô dữ liệu có kích thước 16-bit.

Cần lưu ý rằng trong kiến trúc CPU, register có thể có nhiều kích thước khác nhau như 8-bit, 16-bit, 32-bit, 64-bit hoặc lớn hơn. Tuy nhiên để giải thích Endianness một cách trực quan, bài viết sẽ sử dụng khái niệm register 16-bit.

Vì 1 byte = 8 bit, nên:

  • 1 register 16-bit = 2 byte
  • 1 giá trị 32-bit = 2 register

Ví dụ một register có giá trị hex 0x1234:

0x1234 = 00010010 00110100 (nhị phân)
         [ high byte ][ low byte ]
  • High byte (byte cao): 0x12
  • Low byte (byte thấp): 0x34

Thành phần thường gặp khi đọc register

Khi tài liệu kỹ thuật ghi một địa chỉ register, chúng ta thường cần quan tâm:

  • Address: địa chỉ register (ví dụ 40001, 40002...)
  • Length: số register cần đọc (1, 2, 4...)
  • Data type: kiểu dữ liệu (uint16, int16, uint32, int32, float32...)
  • Byte/Word order: thứ tự byte hoặc thứ tự word khi ghép nhiều register

Vậy tại sao lại có high bytelow byte?

Hiểu đơn giản, khi ta có một số lớn hơn 255 (1 byte), ta cần nhiều byte để lưu trữ nó. Ví dụ, số 0x1234 cần 2 byte. Byte đầu tiên (high byte) chứa phần giá trị lớn hơn, còn byte thứ hai (low byte) chứa phần giá trị nhỏ hơn.

Vì sao high byte và low byte quan trọng?

Với dữ liệu 16-bit, bạn chỉ cần biết high bytelow byte. Nhưng với dữ liệu 32-bit (ghép từ 2 register), ngoài thứ tự byte trong từng register, bạn còn phải để ý thứ tự 2 word 16-bit.

Đó chính là lý do sinh ra các kiểu như ABCD, DCBA, BADC, CDAB mà phần dưới sẽ giải thích chi tiết.


Bắt đầu từ điều cơ bản nhất

Máy tính lưu dữ liệu theo đơn vị nhỏ nhất là byte (8 bit). Khi bạn có một con số lớn hơn — ví dụ số nguyên 32-bit — cần dùng 4 byte liên tiếp trong bộ nhớ.

Câu hỏi đặt ra: byte nào được lưu trước? Byte lớn hay byte nhỏ? Đây chính là vấn đề Endianness (thứ tự byte).

📖 Hãy nghĩ đến cách viết số: Người Việt (và hầu hết thế giới) viết số 1234 từ trái sang phải, chữ số hàng nghìn đứng trước. Nếu ai đó viết ngược lại — 4321 — bạn sẽ đọc ra con số hoàn toàn khác. Endianness trong máy tính cũng là vấn đề tương tự.


Ký hiệu quy ước

Để dễ giải thích, người ta dùng các chữ cái A, B, C, D để đại diện cho 4 byte của một số 32-bit:

Số hex: 0x12345678

A = 0x12  (byte cao nhất — Most Significant Byte)
B = 0x34
C = 0x56
D = 0x78  (byte thấp nhất — Least Significant Byte)

Thứ tự lưu trong bộ nhớ sẽ xác định loại endianness.


Các kiểu thứ tự byte

1. Big Endian — ABCD

Byte lớn nhất lưu trước (địa chỉ thấp nhất).

Địa chỉ:  0x00  0x01  0x02  0x03
Dữ liệu:   12    34    56    78

Đặc điểm: Lưu đúng thứ tự như ta đọc số — byte quan trọng nhất đứng đầu.

Dùng bởi: Giao thức mạng TCP/IP, Motorola 68k, PowerPC, SPARC.


2. Little Endian — DCBA

Byte nhỏ nhất lưu trước (địa chỉ thấp nhất).

Địa chỉ:  0x00  0x01  0x02  0x03
Dữ liệu:   78    56    34    12

Đặc điểm: Ngược với cách con người thường viết số, nhưng giúp một số kiến trúc CPU thuận tiện hơn trong việc mở rộng độ rộng dữ liệu và xử lý các phép toán nhiều byte liên tiếp.

Dùng bởi: Intel x86/x64, AMD và hầu hết các hệ thống ARM hiện đại (Android, iPhone, Raspberry Pi, Linux ARM). Vì vậy đây là kiểu Endianness phổ biến nhất trên các thiết bị ngày nay.


3. Mid Big Endian (Byte-swapped Words) — BADC

Đây là biến thể trong đó thứ tự các word 16-bit vẫn được giữ nguyên, nhưng byte bên trong mỗi word bị đảo ngược.

AB CD
BA DC

Ví dụ với giá trị:

0x12345678

ABCD
BADC

34 12 78 56

Từng dùng bởi: Honeywell 316.

Gặp trong thực tế: Một số thiết bị công nghiệp hoặc giao thức cũ có thể sử dụng kiểu sắp xếp này.


4. Mid Little Endian (Word-swapped) — CDAB

Đây là kiểu lưu giữ nguyên thứ tự byte bên trong mỗi word 16-bit, nhưng hoán đổi vị trí của hai word.

AB CD
CD AB

Ví dụ:

0x12345678

ABCD
CDAB

56 78 12 34

Từng dùng bởi: PDP-11 (DEC).

Gặp trong thực tế: Đây là một trong những định dạng phổ biến nhất khi lưu giá trị 32-bit trong các thiết bị Modbus.


Bảng tổng hợp 4 kiểu

Với số 0x12345678, lưu vào 4 ô nhớ từ địa chỉ 0x00:

TênKý hiệu0x000x010x020x03Ghi chú
Big EndianABCD12345678Network byte order (TCP/IP)
Little EndianDCBA78563412PC, laptop, ARM
Mid Big EndianBADC34127856Honeywell 316, một số hệ đặc thù
Mid Little EndianCDAB56781234PDP-11, một số định dạng word-swapped

Khi nào byte ordering gây ra lỗi thực tế?

Vấn đề chỉ xuất hiện khi dữ liệu đi qua ranh giới hệ thống:

  • Máy A dùng Little Endian, máy B dùng Big Endian
  • File nhị phân được tạo trên kiến trúc này nhưng đọc trên kiến trúc khác
  • Giao tiếp mạng yêu cầu network byte order (Big Endian)
  • Giao thức chỉ chuẩn hóa kích thước dữ liệu nhưng không ép chặt byte/word order cho giá trị 32-bit

Ví dụ cụ thể: Modbus

Modbus quy định mỗi register là 16-bit (2 byte). Khi ghép 2 register thành số 32-bit (float hoặc integer), cách sắp xếp byte/word có thể khác nhau tùy thiết bị.

Cách debug khi đọc sai giá trị

Nếu giá trị đọc ra là 0, một số âm bất thường hoặc các giá trị rất nhỏ như 1.73e-38, nguyên nhân thường là sai byte/word order.

ABCDCDAB là hai định dạng phổ biến nhất trong các thiết bị Modbus.

Khi debug, hãy thử lần lượt cả bốn cách sắp xếp:
   1. ABCD (Big Endian)
   2. CDAB (Mid Little Endian) ← phổ biến nhất trong Modbus
   3. BADC (Mid Big Endian)
   4. DCBA (Little Endian)

Ví dụ code Python — đọc float 32-bit từ Modbus

import struct

# Giả sử đọc được 2 registers: reg1=0x4248, reg2=0x51EC
reg1 = 0x4248
reg2 = 0x51EC

raw_bytes = struct.pack('>HH', reg1, reg2)

# ABCD - Big Endian
val_abcd = struct.unpack('>f', raw_bytes)[0]

# DCBA - Little Endian
val_dcba = struct.unpack('<f', raw_bytes)[0]

# CDAB - Mid Little Endian (hoán đổi word)
raw_cdab = struct.pack('>HH', reg2, reg1)
val_cdab = struct.unpack('>f', raw_cdab)[0]

# BADC - Mid Big Endian (hoán đổi byte trong mỗi word)
raw_badc = struct.pack('>HH',
    ((reg1 & 0xFF) << 8) | (reg1 >> 8),
    ((reg2 & 0xFF) << 8) | (reg2 >> 8)
)
val_badc = struct.unpack('>f', raw_badc)[0]

print(f"ABCD: {val_abcd:.4f}")
print(f"DCBA: {val_dcba:.4f}")
print(f"CDAB: {val_cdab:.4f}")
print(f"BADC: {val_badc:.4f}")

Tóm tắt nhanh

Big Endian (ABCD)Little Endian (DCBA)
Byte đầu tiênMSB — lớn nhấtLSB — nhỏ nhất
Đọc như người viết số✅ Trực quan❌ Ngược
Phổ biến trên PC
Mạng Internet
Dùng làm chuẩn truyền mạng

💡 Mẹo nhớ:

  • Big Endian → byte lớn đứng đầu — như viết số bình thường.
  • Little Endian → byte nhỏ đứng đầu — ngược lại.
  • CDAB / BADC → dạng "mixed" — swap thêm một lần nữa theo word.