728x90
반응형

컴퓨터 시스템에서 데이터를 메모리에 저장하거나 전송할 때 사용하는 바이트 순서(Byte Order) 방식에는 두 가지가 있습니다: 리틀 엔디언(Little Endian)과 빅 엔디언(Big Endian). 이 글에서는 이 두 가지 방식의 차이와 각각의 장단점에 대해 설명합니다.

리틀 엔디언 (Little Endian)

정의

리틀 엔디언 방식에서는 가장 작은 바이트(Least Significant Byte, LSB)를 가장 먼저(가장 낮은 메모리 주소에) 저장합니다.

예시

32비트 정수 0x12345678를 리틀 엔디언 방식으로 메모리에 저장하면 다음과 같이 저장됩니다:

  • 주소 0: 0x78
  • 주소 1: 0x56
  • 주소 2: 0x34
  • 주소 3: 0x12

이 방식은 주로 x86 아키텍처(인텔, AMD)에서 사용됩니다.

코드 예시

// 리틀 엔디언 예제 (C 언어)
#include <stdio.h>

int main() {
    unsigned int value = 0x12345678;
    unsigned char *ptr = (unsigned char *)&value;

    printf("Little Endian Representation:\n");
    for (int i = 0; i < sizeof(value); i++) {
        printf("%02x ", ptr[i]);
    }
    return 0;
}

출력:

78 56 34 12

빅 엔디언 (Big Endian)

정의

빅 엔디언 방식에서는 가장 큰 바이트(Most Significant Byte, MSB)를 가장 먼저(가장 낮은 메모리 주소에) 저장합니다.

예시

32비트 정수 0x12345678를 빅 엔디언 방식으로 메모리에 저장하면 다음과 같이 저장됩니다:

  • 주소 0: 0x12
  • 주소 1: 0x34
  • 주소 2: 0x56
  • 주소 3: 0x78

이 방식은 주로 네트워크 프로토콜 및 여러 RISC 아키텍처(예: PowerPC, SPARC)에서 사용됩니다.

코드 예시

// 빅 엔디언 예제 (C 언어)
#include <stdio.h>

void toBigEndian(unsigned int value) {
    unsigned char bytes[4];
    bytes[0] = (value >> 24) & 0xFF;
    bytes[1] = (value >> 16) & 0xFF;
    bytes[2] = (value >> 8) & 0xFF;
    bytes[3] = value & 0xFF;

    printf("Big Endian Representation:\n");
    for (int i = 0; i < 4; i++) {
        printf("%02x ", bytes[i]);
    }
}

int main() {
    unsigned int value = 0x12345678;
    toBigEndian(value);
    return 0;
}

출력:

12 34 56 78

엔디언의 중요성

호환성

서로 다른 엔디언 방식을 사용하는 시스템 간의 데이터 전송 시 호환성 문제가 발생할 수 있습니다. 이를 해결하기 위해 네트워크 프로토콜은 일반적으로 빅 엔디언 방식을 사용하며, 이를 네트워크 바이트 순서(Network Byte Order)라고 합니다.

성능

일부 아키텍처는 특정 엔디언 방식에서 더 효율적으로 작동할 수 있습니다. 예를 들어, 리틀 엔디언은 x86 아키텍처에서 더 효율적입니다.

엔디언 변환

데이터를 전송하거나 저장할 때 올바른 엔디언 방식을 사용하는 것은 중요합니다. C, C++, 파이썬, 자바스크립트 등 대부분의 프로그래밍 언어는 엔디언 변환 함수를 제공합니다.

자바스크립트 엔디언 변환 예시

자바스크립트에서는 DataView 객체를 사용하여 버퍼 내의 데이터를 원하는 엔디언 형식으로 읽고 쓸 수 있습니다.

// 32비트 정수 값을 버퍼에 리틀 엔디언과 빅 엔디언 형식으로 쓰고 읽는 예제

function toLittleEndian(value) {
    const buffer = new ArrayBuffer(4); // 4바이트 버퍼 생성
    const view = new DataView(buffer);
    view.setUint32(0, value, true); // true는 리틀 엔디언을 의미
    return buffer;
}

function toBigEndian(value) {
    const buffer = new ArrayBuffer(4); // 4바이트 버퍼 생성
    const view = new DataView(buffer);
    view.setUint32(0, value, false); // false는 빅 엔디언을 의미
    return buffer;
}

function bufferToHex(buffer) {
    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(' ');
}

const value = 0x12345678;

const littleEndianBuffer = toLittleEndian(value);
const bigEndianBuffer = toBigEndian(value);

console.log("Little Endian:", bufferToHex(littleEndianBuffer));
console.log("Big Endian:", bufferToHex(bigEndianBuffer));

출력:

Little Endian: 78 56 34 12
Big Endian: 12 34 56 78

결론

리틀 엔디언과 빅 엔디언은 데이터를 메모리에 저장하는 두 가지 방식입니다. 올바른 엔디언 방식을 사용하는 것은 데이터 호환성과 성능에 중요한 영향을 미칠 수 있습니다. 프로그램이 여러 아키텍처나 네트워크 간에 데이터를 주고받을 때는 엔디언 변환을 신경 써야 합니다.

반응형