에듀테크 프로젝트 대시보드
엔디언 (Endianness)
숫자 하나가 메모리에 저장될 때, 여러 바이트를 어떤 순서로 늘어놓을까?
큰 자리(상위 바이트)를 낮은 주소에 두면 빅 엔디언, 작은 자리(하위 바이트)를 낮은 주소에 두면 리틀 엔디언.
같은 값이라도 메모리 덤프를 보면 바이트 순서가 뒤집혀 보이는 이유가 여기 있다.
상위(큰 자리) 바이트가 낮은 주소로. 사람이 숫자 읽는 순서와 같다. 네트워크 표준 순서.
하위(작은 자리) 바이트가 낮은 주소로. x86 · ARM(기본) · RISC-V가 쓴다.
낮은 주소부터 읽으면 곧바로 하위 바이트가 나온다. 그래서 int를 short로,
long을 int로 캐스팅할 때 주소를 그대로 두고 길이만 줄이면 된다.
덧셈·곱셈의 자리올림(carry)도 낮은 자리부터 처리하니 자연스럽다.
메모리 덤프를 왼쪽부터 읽으면 사람이 쓰는 숫자 순서 그대로 보인다. 부호·크기 비교를 앞 바이트부터 할 수 있어 문자열 정렬 같은 사전식 비교에 유리하다. 그래서 네트워크 프로토콜의 표준(“network byte order”)으로 채택됐다.
서로 다른 CPU가 통신하려면 약속이 필요하다. IP·TCP 헤더의 포트·주소 필드는 모두 빅 엔디언.
C에서는 htons()·htonl()(host→network), ntohs()·ntohl()로 변환한다.
ARM·PowerPC는 부팅 시 엔디언을 고를 수 있는 bi-endian. 파일 포맷(PNG는 빅, BMP는 리틀),
WAV·FAT도 제각각이라, 바이너리를 파싱할 땐 스펙에 적힌 엔디언을 꼭 확인해야 한다.
# Python — 시스템 엔디언 확인 import sys, struct print(sys.byteorder) # 'little' 또는 'big' n = 0x12345678 print(struct.pack('>I', n).hex()) # '12345678' (> = big-endian) print(struct.pack('<I', n).hex()) # '78563412' (< = little-endian) # 되돌리기: bytes -> int print(int.from_bytes(bytes.fromhex('78563412'), 'little')) # 305419896
// C — 포인터로 첫 바이트 엿보기 uint32_t x = 0x12345678; unsigned char *p = (unsigned char*)&x; // 리틀 엔디언: p[0]==0x78 빅 엔디언: p[0]==0x12 printf("%s\n", p[0] == 0x12 ? "big" : "little");
| 주소 | 1000 | 1001 | 1002 | 1003 |
|---|---|---|---|---|
| 빅 엔디언 | 0A | 0B | 0C | 0D |
| 리틀 엔디언 | 0D | 0C | 0B | 0A |
최상위 바이트 0A 가 빅 엔디언에서는 맨 앞, 리틀 엔디언에서는 맨 뒤에 온다.
아래 4바이트가 리틀 엔디언으로 저장된 uint32 라면, 원래 값은?
핵심: 엔디언은 바이트 단위 순서일 뿐, 한 바이트 안의 비트 순서는 바꾸지 않는다. 같은 메모리라도 어떤 렌즈(엔디언)로 보느냐에 따라 다른 숫자가 된다.