4 November 2024

리틀 엔디안 빅 엔디안

by 꽈배기

엔디언

엔디언(Endianness)은 컴퓨터 시스템에서 멀티바이트 데이터를 메모리에 저장하거나 전송할 때 바이트의 순서를 정의하는 방식입니다. 주로 두 가지 방식이 있습니다: 리틀 엔디언(Little Endian)과 빅 엔디언(Big Endian).

1. 엔디언 개념 설명

리틀 엔디언 (Little Endian)

리틀 엔디언 방식에서는 데이터의 하위 바이트(Least Significant Byte, LSB)가 메모리의 낮은 주소에 저장됩니다. 즉, 숫자의 가장 작은 단위가 먼저 저장됩니다. 이 방식은 인텔 x86 계열의 프로세서에서 주로 사용됩니다.

예를 들어, 4바이트 정수 0x12345678을 리틀 엔디언 방식으로 메모리에 저장하면 다음과 같이 저장됩니다:

주소: 0x00 0x01 0x02 0x03
값: 0x78 0x56 0x34 0x12

빅 엔디언 (Big Endian)

빅 엔디언 방식에서는 데이터의 상위 바이트(Most Significant Byte, MSB)가 메모리의 낮은 주소에 저장됩니다. 즉, 숫자의 가장 큰 단위가 먼저 저장됩니다. 이 방식은 네트워크 프로토콜과 일부 RISC 프로세서에서 주로 사용됩니다.

예를 들어, 4바이트 정수 0x12345678을 빅 엔디언 방식으로 메모리에 저장하면 다음과 같이 저장됩니다:

주소: 0x00 0x01 0x02 0x03
값: 0x12 0x34 0x56 0x78

2. 구체적인 예시

다음은 C++로 리틀 엔디언과 빅 엔디언을 확인하고 변환하는 예제입니다.

#include
#include

// 엔디언을 확인하는 함수
bool isLittleEndian() {
 uint16_t number = 0x1;
 uint8_t *bytePtr = reinterpret_cast<uint8_t*>(&number);
 return (bytePtr[0] == 0x1);
}

// 엔디언 변환 함수 (리틀 엔디언 <-> 빅 엔디언)
uint32_t swapEndian(uint32_t num) {
 return ((num >> 24) & 0x000000FF) |
 ((num >> 8) & 0x0000FF00) |
 ((num << 8) & 0x00FF0000) |
 ((num << 24) & 0xFF000000);
}

int main() {
 uint32_t num = 0x12345678;

 std::cout << "Original number: 0x" << std::hex << num << std::endl;

 if (isLittleEndian()) {
 std::cout << "System is Little Endian" << std::endl;
 } else {
 std::cout << "System is Big Endian" << std::endl;
 }

 uint32_t swappedNum = swapEndian(num);
 std::cout << "Swapped number: 0x" << std::hex << swappedNum << std::endl;

 return 0;
}

설명

  1. isLittleEndian 함수는 시스템이 리틀 엔디언인지 빅 엔디언인지 확인합니다. 16비트 정수 0x1를 바이트 포인터로 변환하여 첫 번째 바이트가 0x1인지 확인합니다. 만약 그렇다면, 시스템은 리틀 엔디언입니다.
  2. swapEndian 함수는 32비트 정수의 바이트 순서를 바꿉니다. 비트 연산을 통해 각 바이트를 적절한 위치로 이동시킵니다.
  3. main 함수에서는 원래 숫자를 출력하고, 시스템의 엔디언 타입을 확인한 후, 바이트 순서를 바꾼 숫자를 출력합니다.

좋다 그렇다면 LittleEndian이 메모리 관점에서 어떻게 되는지 보자면 아래와 같이 저장된다.

image

저장된 num의 메모리 주소에 접근해보면, 메모리 num 기준 0x00에는 78, 0x01에는 56 순서대로 가장 낮은 숫자의 단위가 가장 먼저 저장되는 것을 볼 수 있다.


그 다음 swapEndian의 경우 아래와 같은 로직으로 진행된다.

주어진 함수 swapEndian는 32비트 정수의 바이트 순서를 바꾸는 역할을 합니다.

uint32_t swapEndian(uint32_t num) {
 return ((num >> 24) & 0x000000FF) |
 ((num >> 8) & 0x0000FF00) |
 ((num << 8) & 0x00FF0000) |
 ((num << 24) & 0xFF000000);
}

모든 과정은 반복되는데, 비트를 쉬프트 한 결과에 비트 & 연산을 수행함으로 겹치는 부분만 추출하는 방식이다.

  1. 첫 번째 바이트 추출 및 이동
     (num >> 24) & 0x000000FF
    
    • num을 (2진수 기준 2^4^, 32 - FF 이므로) 24비트 오른쪽으로 이동시킵니다. 이렇게 하면 원래 num의 가장 왼쪽 바이트 가 가장 오른쪽 바이트로 이동합니다.
  1. 두 번째 바이트 추출 및 이동
     (num >> 8) & 0x0000FF00
    
    • num을 8비트 오른쪽으로 이동시킵니다. 이렇게 하면 원래 num의 두 번째 바이트가 가장 오른쪽 바이트로 이동합니다.
  1. 세 번째 바이트 추출 및 이동
     (num << 8) & 0x00FF0000
    
    • num을 8비트 왼쪽으로 이동시킵니다. 이렇게 하면 원래 num의 세 번째 바이트가 두 번째 바이트로 이동합니다.
    • & 0x00FF0000는 상위 1바이트와 하위 2바이트를 0으로 만들고, 세 번째 바이트만 남깁니다.
    • 예를 들어, num0x12345678이라면, num << 80x34567800가 되고, & 0x00FF0000를 적용하면 0x00560000가 됩니다.
  2. 네 번째 바이트 추출 및 이동
     (num << 24) & 0xFF000000
    
    • num을 24비트 왼쪽으로 이동시킵니다. 이렇게 하면 원래 num의 네 번째 바이트가 가장 왼쪽 바이트로 이동합니다.
    • & 0xFF000000는 하위 3바이트를 0으로 만들고, 네 번째 바이트만 남깁니다.
    • 예를 들어, num0x12345678이라면, num << 240x78000000가 되고, & 0xFF000000를 적용하면 여전히 0x78000000가 됩니다.
  3. 결과 합산
     return ((num >> 24) & 0x000000FF) |
     ((num >> 8) & 0x0000FF00) |
     ((num << 8) & 0x00FF0000) |
     ((num << 24) & 0xFF000000);
    
    • 각 바이트를 올바른 위치로 이동시킨 후, 비트 OR 연산(|)을 사용하여 합칩니다.
    • 예를 들어, num0x12345678이라면, 각 단계의 결과는 다음과 같습니다:
    • 0x00000012
    • 0x00003400
    • 0x00560000
    • 0x78000000
    • 이들을 OR 연산으로 합치면 최종 결과는 0x78563412가 됩니다.

이 함수는 주어진 32비트 정수의 바이트 순서를 반대로 바꾸어 리틀 엔디안에서 빅 엔디안으로, 또는 그 반대로 변환합니다.

요약
음 그렇다. 엔디안 변환법은 비트 시프트와 &연산으로 필요한 부분만 마스킹하고 나머지 결과들을 모두 연산으로 합치는것이다.

Q: 그렇다면 왜 이 두가지 방식으로 데이터 바이트 순서를 정할까?

A
리틀 엔디안, 빅 엔디안(Big Endian)은 데이터의 바이트 순서를 다루는 방식으로 데이터가 메모리에 저장되는 순서와 관련이 있습니다. 각각의 방식이 특정 상황에서 더 효율적인 이유는 아키텍처 내부적인 설계와 관련이 있습니다.

리틀 엔디안 방식의 효율성

리틀 엔디안 방식이 물리적으로 데이터를 조작하거나 산술 연산을 수행할 때 더 효율적인 이유는 다음과 같습니다

  1. 하드웨어 설계의 단순성: 리틀 엔디안 방식에서는 가장 낮은 바이트가 가장 낮은 주소에 저장되므로, 하드웨어가 데이터를 읽고 쓸 때 추가적인 주소 계산이 필요 없습니다. 이는 특히 8비트, 16비트, 32비트 등의 다양한 크기의 데이터를 처리할 때 유리합니다.
  2. 증분 연산의 효율성: 리틀 엔디안 방식에서는 정수의 하위 바이트부터 상위 바이트로 연산이 진행되므로, 하드웨어가 덧셈, 뺄셈 등의 산술 연산을 수행할 때 캐리(carry) 처리가 더 간단합니다. (즉 연산시에 순서를 안바꿔도 되니까 편하단거다)

빅 엔디안 방식의 적합성

빅 엔디안 방식이 데이터의 각 바이트를 배열처럼 취급할 때 더 적합한 이유는 다음과 같습니다

  1. 가독성: 빅 엔디안 방식에서는 데이터가 메모리에 저장된 순서가 사람이 읽는 순서와 일치합니다. 이는 디버깅이나 데이터 분석 시 가독성을 높여줍니다.
  2. 네트워크 프로토콜: 많은 네트워크 프로토콜이 빅 엔디안 방식을 사용합니다. 이는 서로 다른 시스템 간의 데이터 교환 시 일관성을 유지하기 위함입니다.

32비트 및 64비트 운영체제에서의 엔디안

운영체제의 비트 수와 엔디안 방식은 직접적인 연관이 없습니다. 엔디안 방식은 주로 CPU 아키텍처에 의해 결정됩니다. 그러나 특정 운영체제는 특정 엔디안 방식을 더 선호할 수 있습니다.

결론적으로, 엔디안 방식은 CPU 아키텍처와 밀접한 관련이 있으며, 특정 상황에서의 효율성은 하드웨어 설계와 데이터 처리 방식에 따라 달라집니다.

tags: ComputerScience