Programming Language/C

[C] 리틀엔디안(Little Endian), 빅엔디안(Big Endian)

장영현 2023. 6. 28. 19:41
728x90

리틀엔디안(Little Endian)과 빅엔디안(Big Endian)은 컴퓨터 시스템에서 데이터를 저장하고 표현하는 방식을 나타내는 개념이다. 이것은 특히 컴퓨터의 바이트 순서를 의미한다.

리틀엔디안은 가장 낮은 자릿수의 바이트부터 메모리에 저장하는 방식이다. 즉, 가장 작은 단위인 바이트의 최하위 비트부터 차례로 메모리에 저장된다. 예를 들어, 32비트 정수 0x12345678을 리틀엔디안 방식으로 저장하면 메모리에는 0x78, 0x56, 0x34, 0x12와 같은 순서로 저장된다. 이는 바이트의 순서가 역순으로 되어있다고 볼 수 있다.

반면에 빅엔디안은 가장 높은 자릿수의 바이트부터 메모리에 저장하는 방식이다. 즉, 가장 큰 단위인 바이트의 최상위 비트부터 차례로 메모리에 저장된다. 예를 들어, 32비트 정수 0x12345678을 빅엔디안 방식으로 저장하면 메모리에는 0x12, 0x34, 0x56, 0x78과 같은 순서로 저장된다. 이는 바이트의 순서가 원래의 순서와 동일하다고 볼 수 있다.

 

리틀 엔디안, 빅 엔디안


C 언어는 기본적으로 플랫폼의 네이티브 바이트 순서를 따른다. 즉, 리틀엔디안 아키텍처를 사용하는 시스템에서는 리틀엔디안을, 빅엔디안 아키텍처를 사용하는 시스템에서는 빅엔디안을 기본적으로 사용한다. 하지만 C 언어에서는 리틀엔디안과 빅엔디안을 명시적으로 다룰 수 있는 방법도 제공된다. 이를 통해 어떤 바이트 순서를 사용해야 하는지 조작할 수 있다.

예를 들어, 리틀엔디안 아키텍처에서 빅엔디안 순서로 데이터를 저장하거나, 빅엔디안 아키텍처에서 리틀엔디안 순서로 데이터를 저장하는 등의 작업을 수행할 수 있다. 이는 특히 다른 시스템과 데이터를 교환해야 하는 경우나 데이터 직렬화와 같은 작업에서 유용하다. 이를 위해 C 언어에서는 바이트 순서를 변환하는 함수나 비트 연산자 등의 기능을 제공한다.

 

 

 


 

 

  • 엔디안 예제 코드
  • endian.h 사용

리틀엔디안인지 빅엔디안인지 확인하는 다른 방법 중 하나는 stdint.h 헤더 파일에 정의된 endian.h 라이브러리를 사용하는 것이다. 이 라이브러리에는 엔디안 확인을 위한 매크로가 정의되어 있다.

//엔디안 확인 코드
#include <stdio.h>
#include <stdint.h>
#include <endian.h>

int main() {
    if (__BYTE_ORDER == __LITTLE_ENDIAN) {
        printf("Little-endian\n");
    } else if (__BYTE_ORDER == __BIG_ENDIAN) {
        printf("Big-endian\n");
    } else {
        printf("Unknown endianness\n");
    }

    return 0;
}

위 코드에서 __BYTE_ORDER 매크로는 현재 시스템의 엔디안을 나타내는 상수이다. __LITTLE_ENDIAN과 __BIG_ENDIAN은 각각 리틀엔디안과 빅엔디안을 나타내는 상수이다.

이 코드를 실행하면 현재 시스템의 엔디안 방식에 따라 "Little-endian", "Big-endian" 또는 "Unknown endianness"라는 결과가 출력된다.

 

 

 

  • 포인터 사용

<endian.h> 헤더 파일을 사용하지 않고도 현재 시스템의 엔디안을 확인할 수 있는 방법 중 하나는 포인터를 사용하는 방법이다.

#include <stdio.h>

int main() {
    unsigned int value = 1;
    unsigned char* byte_ptr = (unsigned char*)&value;

    if (*byte_ptr == 1) {
        printf("Little-endian\n");
    } else {
        printf("Big-endian\n");
    }

    return 0;
}

더보기

unsigned char* byte_ptr = (unsigned char*)&value;는 변수 value의 주소를 unsigned char 포인터인 byte_ptr에 대입하는 코드다.

  1. &value는 value 변수의 주소를 나타낸다. & 연산자를 사용하여 변수의 주소를 가져온다.
  2. unsigned char*는 unsigned char 형식의 포인터를 선언하는 것을 의미한다. byte_ptr은 unsigned char 형식을 가리키는 포인터다.
  3. (unsigned char*)&value는 &value에서 가져온 주소를 unsigned char*로 형변환하여 byte_ptr에 대입한다. 이로써 byte_ptr은 value 변수의 주소를 가리키게 된다.

이 코드의 목적은 value 변수의 메모리 주소를 unsigned char 포인터로 캐스팅하여 각 바이트에 접근할 수 있도록 하는 것이다. 이를 통해 엔디안 여부를 확인할 수 있다.


위 코드에서 unsigned int 변수 value를 선언하고 1로 초기화한다. 그리고 unsigned char 포인터 byte_ptr를 value 변수의 주소로 설정한다.

리틀엔디안 시스템에서는 가장 낮은 자릿수의 바이트가 메모리의 낮은 주소에 위치하므로, byte_ptr가 가리키는 값이 1이라면 현재 시스템은 리틀엔디안 방식으로 데이터를 저장하는 시스템이다. 따라서 "Little-endian"이라는 문자열을 출력한다.

만약 byte_ptr가 가리키는 값이 1이 아니라면, 현재 시스템은 빅엔디안 방식으로 데이터를 저장하는 시스템이다. 이 경우 "Big-endian"이라는 문자열을 출력한다.

따라서 이 코드를 실행하면 현재 시스템의 엔디안 방식에 따라 "Little-endian" 또는 "Big-endian"이라는 결과가 출력된다.

 

 

 


 

 

  • 빅엔디안 -> 리틀엔디안 변환
  • 포인터를 사용한 방법
#include <stdio.h>

// 빅엔디안과 리틀엔디안 간의 16비트 데이터 교환 함수
unsigned short swap16(unsigned short value) {
    // 데이터를 8비트 단위로 나누고, 마지막과 처음의 위치를 바꿔서 재조합
    return (value >> 8) | (value << 8);
}

// 빅엔디안과 리틀엔디안 간의 32비트 데이터 교환 함수
unsigned int swap32(unsigned int value) {
    // 데이터를 8비트 단위로 나누고, 위치를 바꿔서 재조합
    return ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);
}

int main() {
    // 빅엔디안 형태의 데이터
    unsigned short bigEndian16 = 0xABCD;
    unsigned int bigEndian32 = 0x12345678;

    // 빅엔디안에서 리틀엔디안으로 변환
    unsigned short littleEndian16 = swap16(bigEndian16);
    unsigned int littleEndian32 = swap32(bigEndian32);

    printf("빅엔디안 16비트: 0x%04X\n", bigEndian16);
    printf("리틀엔디안 16비트: 0x%04X\n", littleEndian16);
    printf("빅엔디안 32비트: 0x%08X\n", bigEndian32);
    printf("리틀엔디안 32비트: 0x%08X\n", littleEndian32);

    return 0;
}

 

코드 설명

unsigned short swap16(unsigned short value) {
    return (value >> 8) | (value << 8);
}

함수의 매개변수로 들어온 value는 리틀엔디안 형태의 16비트 데이터입니다. 이 데이터의 바이트 순서를 빅엔디안으로 변환하여 리턴한다.

코드 내부에서는 비트 시프트 연산과 비트 OR 연산을 사용한다. value를 오른쪽으로 8비트 시프트(value >> 8)하여 상위 8비트를 하위 8비트로 이동시키고, value를 왼쪽으로 8비트 시프트(value << 8)하여 하위 8비트를 상위 8비트로 이동시킨다. 그리고 두 결과를 비트 OR(|) 연산하여 하나의 16비트 값으로 합친다.

 

매개변수의 자료형을 short로 잡은 이유는 16비트 데이터를 표현하기 위해서이다.

리틀엔디안과 빅엔디안은 데이터의 표현 방식에 따라 바이트 순서가 다르다. 16비트 데이터는 2바이트를 사용하여 표현되며, 이를 변환하기 위해서는 2바이트 단위로 작업해야 한다. 따라서 short 자료형을 사용하여 16비트 데이터를 표현하고 처리하기에 적합하다.

 

unsigned int swap32(unsigned int value) {
    return ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);
}

함수의 매개변수로 들어온 value는 리틀엔디안 형태의 32비트 데이터다. 이 데이터의 바이트 순서를 빅엔디안으로 변환하여 리턴한다.

코드 내부에서는 비트 시프트 연산과 비트 AND 연산, 비트 OR 연산을 사용헌다. value를 오른쪽으로 24비트 시프트(value >> 24)하여 상위 8비트를 하위 8비트로 이동시킨다. 그리고 value를 오른쪽으로 8비트 시프트(value >> 8)하여 다시 상위 8비트를 하위 8비트로 이동시킨다. 이렇게 이동시킨 값과 비트 AND 연산(&)을 수행하여 하위 8비트를 추출한다.

동일한 방식으로 value를 왼쪽으로 8비트 시프트(value << 8)하여 하위 8비트를 상위 8비트로 이동시키고, 다시 왼쪽으로 24비트 시프트(value << 24)하여 상위 8비트를 하위 8비트로 이동시킨다. 이렇게 이동시킨 값과 비트 AND 연산을 수행하여 상위 8비트를 추출한다.

그리고 각각의 결과를 비트 OR(|) 연산하여 하나의 32비트 값으로 합친다.

 

((value >> 24) & 0xFF) ((value >> 8) & 0xFF00) 

 

((value << 8) & 0xFF0000) ((value << 24) & 0xFF000000)

위의 과정으로 자리 변경된다.

 

  • 공용체를 사용한 방법
#include <stdio.h>

union EndianConverter {
    unsigned short value16;
    unsigned int value32;
};

int main() {
    // 빅엔디안 형태의 데이터
    unsigned short bigEndian16 = 0xABCD;
    unsigned int bigEndian32 = 0x12345678;

    union EndianConverter converter;

    // 빅엔디안에서 리틀엔디안으로 변환
    converter.value16 = bigEndian16;
    unsigned short littleEndian16 = converter.value16;

    converter.value32 = bigEndian32;
    unsigned int littleEndian32 = converter.value32;

    printf("빅엔디안 16비트: 0x%04X\n", bigEndian16);
    printf("리틀엔디안 16비트: 0x%04X\n", littleEndian16);
    printf("빅엔디안 32비트: 0x%08X\n", bigEndian32);
    printf("리틀엔디안 32비트: 0x%08X\n", littleEndian32);

    return 0;
}

 

 


 

 

  • 리틀엔디안 -> 빅엔디안 변환
#include <stdio.h>

// 빅엔디안과 리틀엔디안 간의 16비트 데이터 교환 함수
unsigned short swap16(unsigned short value) {
    return (value >> 8) | (value << 8);
}

// 빅엔디안과 리틀엔디안 간의 32비트 데이터 교환 함수
unsigned int swap32(unsigned int value) {
    return ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);
}

int main() {
    // 리틀엔디안 형태의 데이터
    unsigned short littleEndian16 = 0xCDAB;
    unsigned int littleEndian32 = 0x78563412;

    // 리틀엔디안에서 빅엔디안으로 변환
    unsigned short bigEndian16 = swap16(littleEndian16);
    unsigned int bigEndian32 = swap32(littleEndian32);

    printf("리틀엔디안 16비트: 0x%04X\n", littleEndian16);
    printf("빅엔디안 16비트: 0x%04X\n", bigEndian16);
    printf("리틀엔디안 32비트: 0x%08X\n", littleEndian32);
    printf("빅엔디안 32비트: 0x%08X\n", bigEndian32);

    return 0;
}

 

((value >> 24) & 0xFF) ((value >> 8) & 0xFF00) 

 

 ((value << 8) & 0xFF0000) ((value << 24) & 0xFF000000)