공용체(Union)와 리틀 엔디언(Little endian)

엔디언이란 메모리에 바이트를 배열하는 방식을 말하며, 주로 사용되는 엔디언으로는 빅 엔디언(Big Endian)과 리틀 엔디언(Little Endian) 두 가지가 있다.

빅 엔디언

사람이 쓰는 것과 동일하게 큰 자릿수(큰 바이트)가 앞에 위치하고, 작은 자릿수가 뒤에 위치한다.

리틀 엔디언

빅 엔디언과 반대로 작은 자릿수(작은 바이트)가 앞에 위치하고, 큰 자릿수가 뒤에 위치한다.

아래 표로 보면 이해가 쉽다.

종류0x 1234 5678의 표현
빅 엔디언12 34 56 78
리틀 엔디언78 56 34 12

리틀 엔디언은 x86 아키텍처에서 사용하는 방법으로 대부분의 경우에는 리틀 엔디언이라고 생각하면 된다. ARM 프로세서의 경우 빅 엔디언과 리틀 엔디언을 선택할 수 있다. 각 엔디언의 장단점 비교는 위키피디아를 참조.

리틀 엔디언에서 공용체를 사용하여 메모리를 공유할 경우 바이트 저장 순서를 확인할 수 있는 예제를 소개하겠다.

#include <stdio.h>

struct DeviceOption{
    union {
        int option;
        struct{
            unsigned char power;
            unsigned char mode;
            unsigned char mouse;
            unsigned char keyboard;
        };
    };
};

int main()
{
    struct DeviceOption opt;

    // struct 저장 순서 11 -> 22 -> 33 -> 44 이지만
    // 리틀 엔디언이므로 메모리에는 반대로 저장됨
    opt.power = 0x11;
    opt.mode = 0x22;
    opt.mouse = 0x33;
    opt.keyboard = 0x44;

    printf("0x%x\n", opt.option); // 0x44331122

    return 0;
}

실행 결과

0x44331122

마지막으로 리틀 엔디언에서 포인터 형 변환 예제를 통해 메모리 주소를 저장하고 불러올 때를 확인해보자.

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

int main()
{
    int *numPtr = malloc(sizeof(int));    // sizeof(int) == 4
    char *charPtr;

    *numPtr = 0x12345678;

    charPtr = (char *)numPtr;

    printf("0x%x\n", *charPtr);   // 0x78: 낮은 자릿수 1바이트를 읽음

    free(numPtr);

    return 0;
}

마찬가지로 실행 결과는

0x78

이다. 주소값은 int형 포인터이므로 메모리 상에 78 56 34 12 순으로 저장된다. char형 포인터로 형변환을 시도하면 1바이트만 읽어올 수 있으므로 가장 앞의 바이트인 78을 읽어들이는데, 이는 원래 메모리 주소 0x12345678에서 가장 낮은 자릿수 78과 같다.