Skip to content

49. 구조체 비트 필드


1. 키워드

  • 구조체 비트 필드(Bit Field)


2. 구조체 비트 필드 사용하기

  • 지금까지 구조체의 멤버는 각 자료형 크기만큼 공간을 차지했다.
  • 하지만 구조체 비트 필드를 사용하면 구조체 멤버를 비트 단위로 저장할 수 있다.
  • 특히 CPU나 기타 칩의 플래그를 다루는 저수준(Low Level) 프로그래밍을 할 때 기본 자료형보다 더 작은 비트 단위로 값을 가져오거나 저장하는 경우가 많으므로 구조체 비트 필드가 유용하게 사용된다.


3. 구조체 비트 필드를 만들고 사용하기

  • C99 표준에서는 비트 필드로 사용할 수 있는 자료형을 _Bool, signed int, unsigned int, int 타입으로 규정하고 있지만, 대부분의 컴파일러에서는 모든 정수 자료형을 사용할 수 있다.
  • 보통은 비트 필드에 부호 없는(unsigned) 자료형을 주로 사용한다.
  • 단, 실수 자료형은 비트 필드로 사용할 수 없다.


  • 비트 필드는 다음과 같이 멤버를 선언할 때 :(콜론) 뒤에 비트 수를 지정해 주면 된다.


struct 구조체이름 {
    정수자료형 멤버이름 : 비트수;
};


  • 이제 구조체를 7비트, 3비트, 1비트로 나눠서 비트 필드를 정의해 보자.


001


#include <stdio.h>

struct Flags
{
    unsigned int a : 1; // a는 1비트 크기
    unsigned int b : 3; // b는 3비트 크기
    unsigned int c : 7; // c는 7비트 크기
};

int main()
{
    struct Flags f1; // 구조체 변수 선언

    f1.a = 1;   // 0000 0001, 비트 1개
    f1.b = 15;  // 0000 1111, 비트 4개
    f1.c = 255; // 1111 1111, 비트 8개

    printf("%u\n", f1.a); //        1, 비트 1개만 저장됨
    printf("%u\n", f1.b); //      111, 비트 3개만 저장됨
    printf("%u\n", f1.c); // 111 1111, 비트 7개만 저장됨

    return 0;
}

// 1
// 7
// 127


  • 비트 필드에는 지정한 비트 수만큼 저장되며 나머지 비트는 버려진다.
  • 따라서 a는 비트 그대로 1만 저장되었고, b는 비트 3개만 저장되었으므로 7, c는 비트 7개만 저장되었으므로 127이 된다.


  • 다음과 같이 비트 필드의 각 멤버는 최하위 비트(LSB)부터 차례대로 배치된다.
  • 따라서 a가 최하위 비트에 오고 나머지 멤버들은 각각 상위 비트에 배치된다.


002


  • sizeof 연산자로 Flags 비트 필드 구조체의 크기를 구해 보면 4가 나온다.


#include <stdio.h>

struct Flags
{
    unsigned int a : 1; // a는 1비트 크기
    unsigned int b : 3; // b는 3비트 크기
    unsigned int c : 7; // c는 7비트 크기
};

int main()
{
    printf("%d", sizeof(struct Flags)); // 멤버를 unsigned int 타입으로 선언했으므로 4

    return 0;
}

// 4


  • 비트 필드의 멤버를 unsigned int 타입으로 선언했으므로 구조체의 크기는 4가 된다.
  • 만약 멤버를 unsigned short 타입으로 선언하면 구조체의 크기는 2가 나온다.


비트 필드 비트 수 제한

  • 다음과 같이 비트 필드의 멤버를 선언하는 자료형보다 큰 비트 수는 지정할 수 없다.


struct Flags
{
    unsigned int a : 37; // 컴파일 에러. unsigned int 타입보다 큰 비트 수는 지정할 수 없음
    unsigned int b : 3;
    unsigned int c : 7;
};


4. 비트 필드와 공용체를 함께 사용하기

  • 보통 사람이 코드에서 값을 지정할 때는 비트 필드를 사용하지만 CPU나 칩에 값을 설정할 때는 모든 비트를 묶어서 한꺼번에 저장한다.
  • 이번에는 비트 필드의 값을 한꺼번에 사용할 수 있도록 비트 필드와 공용체를 함께 사용해 보자.


003


#include <stdio.h>

struct Flags
{
    union // 익명 공용체
    {
        struct // 익명 구조체
        {
            unsigned short a : 3; // a는 3비트 크기
            unsigned short b : 2; // b는 2비트 크기
            unsigned short c : 7; // c는 7비트 크기
            unsigned short d : 4; // d는 4비트 크기
        };                        // 합계 16비트
        unsigned short e;         // 2바이트(16비트)
    };
};

int main()
{
    struct Flags f1 = { // 모든 멤버를 0으로 초기화
        0,
    };

    f1.a = 4;  //  4: 0000 0100
    f1.b = 2;  //  2: 0000 0010
    f1.c = 80; // 80: 0101 0000
    f1.d = 15; // 15: 0000 1111

    printf("%u\n", f1.e); // 1111 1010000 10 100

    return 0;
}

// 64020


  • 먼저 비트 필드로 사용할 멤버는 익명 구조체로 감싸준다.
  • 그리고 비트 필드의 값을 한꺼번에 접근할 수 있도록 unsigned short 타입 멤버를 선언하고 익명 공용체로 감싸준다.
  • 여기서는 2바이트(16비트) 크기의 unsigned short 타입에 정확히 맞추기 위해 비트 수를 3, 2, 7, 4로 지정했다.


struct Flags
{
    union // 익명 공용체
    {
        struct // 익명 구조체
        {
            unsigned short a : 3; // a는 3비트 크기
            unsigned short b : 2; // b는 2비트 크기
            unsigned short c : 7; // c는 7비트 크기
            unsigned short d : 4; // d는 4비트 크기
        };                        // 합계 16비트
        unsigned short e;         // 2바이트(16비트)
    };
};


  • 이제 비트 필드에 값을 할당한다.
  • 여기서는 각 비트 수에 맞게 값을 할당했다.


f1.a = 4;  //  4: 0000 0100
f1.b = 2;  //  2: 0000 0010
f1.c = 80; // 80: 0101 0000
f1.d = 15; // 15: 0000 1111


  • 공용체로 감싸준 멤버 eprintf로 출력해 보면 64020이 나온다.
  • 즉, 비트 필드에 할당한 비트들을 차례대로 연결하면 1111 1010000 10 100이 되므로 10진수로 표현했을 때 64020이 된다.


printf("%u\n", f1.e); // 1111 1010000 10 100


004


References