비트 필드(Bit-field) — 비트 단위로 멤버를 배치하기

비트 필드(Bit-field) — 비트 단위로 멤버를 배치하기

구조체 멤버를 바이트 단위가 아니라 비트 단위로 쪼개서 쓰고 싶을 때가 있어요. 플래그 몇 개만 담는 변수에 int 하나(32비트)를 통째로 쓰기엔 낭비처럼 느껴지죠. C의 **비트 필드(bit-field)**는 멤버에 명시적인 비트 폭을 지정해서, 값의 범위를 꽉 채워 쓰고 여러 멤버를 밀착시켜 패킹하는 방법을 제공해요.

출처: cppreference

본문

비트 필드는 비트 단위의 폭을 가진 멤버를 선언해요. 인접한 비트 필드 멤버는 개별 바이트를 공유하거나 걸쳐서( straddle) 패킹될 수 있어요.

비트 필드 선언은 다음 선언자를 사용하는 구조체 또는 공용체 멤버 선언이에요.

identifier(optional) : width
  • identifier — 선언되는 비트 필드의 이름. 선택적이에요. 이름 없는 비트 필드는 지정된 수의 비트를 패딩으로 도입해요.
  • width — 0 이상, 기본 타입의 비트 수 이하의 값을 가진 정수 상수 표현식(constant expression). 0보다 크면 비트 필드가 차지할 비트 수예요. 값 0은 이름 없는 비트 필드에서만 허용되고, 클래스 정의의 다음 비트 필드가 할당 단위( allocation unit)의 경계에서 시작한다는 특별한 의미로 쓰여요.

설명(Explanation)

비트 필드는 ( constvolatile로 한정될 수 있는) 다음 타입 중 하나만 가질 수 있어요.

  • unsigned int — 부호 없는 비트 필드용. 예: unsigned int b : 3;은 범위 [0, 7]을 가져요.
  • signed int — 부호 있는 비트 필드용. 예: signed int b : 3;은 범위 [-4, 3]을 가져요.
  • int — 부호 여부가 구현 정의인 비트 필드용. 여기서 int는 다른 모든 곳과 달리 "signed int"를 뜻하지 않아요. 예: int b : 3;[0, 7] 또는 [-4, 3] 범위를 가질 수 있어요.
  • _Bool — 단일 비트 비트 필드용 (C99부터). 예: bool x : 1;은 범위 [0, 1]을 가지고, 이 타입과의 암묵적 변환은 불리언 변환 규칙을 따라요.
  • 비트 정밀 정수 타입 (C23부터). 예: _BitInt(5) : 4;[-8, 7], unsigned _BitInt(5) : 4;[0, 15] 범위를 가져요.

추가적인 구현 정의 타입도 허용될 수 있어요. 비트 필드가 원자 타입(atomic type)을 가질 수 있는지도 구현 정의예요 (C11부터).

비트 필드의 비트 수(폭, width)는 담을 수 있는 값의 범위 한계를 정해줘요.

#include <stdio.h>

struct S
{
    // three-bit unsigned field,
    // allowed values are 0...7
    unsigned int b : 3;
};

int main(void)
{
    struct S s = {7};
    ++s.b; // unsigned overflow
    printf("%d\n", s.b); // output: 0
}

인접한 여러 비트 필드는 (보통 그렇게 하지만) 함께 패킹될 수 있어요.

#include <stdio.h>

struct S
{
    // will usually occupy 4 bytes:
    // 5 bits: value of b1
    // 11 bits: unused
    // 6 bits: value of b2
    // 2 bits: value of b3
    // 8 bits: unused
    unsigned b1 : 5, : 11, b2 : 6, b3 : 2;
};

int main(void)
{
    printf("%zu\n", sizeof(struct S)); // usually prints 4
}

특별한 **이름 없는 비트 필드(width 0)**는 패딩을 끊어요. 즉 다음 비트 필드가 다음 할당 단위의 처음에서 시작하도록 지정해요.

#include <stdio.h>

struct S
{
    // will usually occupy 8 bytes:
    // 5 bits: value of b1
    // 27 bits: unused
    // 6 bits: value of b2
    // 15 bits: value of b3
    // 11 bits: unused
    unsigned b1 : 5;
    unsigned    : 0; // starts a new unsigned int
    unsigned b2 : 6;
    unsigned b3 : 15;
};

int main(void)
{
    printf("%zu\n", sizeof(struct S)); // usually prints 8
}

비트 필드는 반드시 바이트의 처음에서 시작하지 않으므로, 비트 필드의 주소를 얻을 수 없어요. 비트 필드에 대한 포인터도 만들 수 없어요. 비트 필드는 sizeof_Alignas(C11~C23)/alignas(C23부터)와 함께 쓸 수 없어요.

참고(Notes)

비트 필드의 다음 쓰임은 **미정의 동작(undefined behavior)**을 일으켜요.

  • 비트 필드에 offsetof를 호출하는 것.

비트 필드의 다음 속성은 **불특정(unspecified)**이에요.

  • 비트 필드를 담는 할당 단위의 정렬(alignment).

비트 필드의 다음 속성은 **구현 정의(implementation-defined)**예요.

  • int 타입 비트 필드를 부호 있는 것으로 볼지 부호 없는 것으로 볼지.
  • int, signed int, unsigned int, _Bool(C99부터), (아마 unsigned인) _BitInt(N)(C23부터) 외의 타입이 허용되는지 여부.
  • 원자 타입이 허용되는지 여부 (C11부터).
  • 비트 필드가 할당 단위 경계를 걸칠 수 있는지 여부.
  • 할당 단위 안에서 비트 필드의 순서 (어떤 플랫폼은 왼쪽→오른쪽, 어떤 플랫폼은 오른쪽→왼쪽으로 패킹해요).

_Bool의 객체 표현의 비트 수가 최소한 CHAR_BIT임에도, _Bool 타입 비트 필드의 폭은 1보다 클 수 없어요 (C99부터).

C++에서는 비트 필드의 폭이 기본 타입의 폭을 초과할 수 있고(추가 비트는 패딩 비트), int 타입 비트 필드는 항상 부호 있습니다.

더 알아보기