비트 필드(Bit-field)

비트 필드(Bit-field)

구조체나 공용체 안에서 어떤 멤버가 차지하는 크기를 바이트가 아니라 비트 단위로 지정하고 싶을 때가 있어요. 그럴 때 쓰는 것이 바로 **비트 필드(bit-field)**예요. 비트 필드는 명시된 폭(비트 단위)을 가진 멤버 선언이고, 서로 인접한 비트 필드 멤버들은 묶여서(packed) 개별 바이트를 공유하거나 걸쳐서(straddle) 차지할 수도 있어요.

비트 필드 선언은 다음 선언자(declarator)를 사용하는 struct/union 멤버 선언이에요.

identifier(optional) : width
자리 의미
identifier 선언되는 비트 필드의 이름. 이름은 생략할 수 있어요. 이름 없는 비트 필드는 지정된 개수의 패딩 비트를 도입해요.
width 값이 0 이상, 기반 타입의 비트 수 이하인 정수 상수 표현식. 0보다 크면 이 비트 필드가 차지할 비트 수예요. 값 0은 이름 없는 비트 필드에만 허용되며 특별한 의미가 있어요. 다음 비트 필드가 할당 단위(allocation unit)의 경계에서 시작하도록 지정하는 거예요.

출처: cppreference

본문

동작 방식(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 — 1비트 비트 필드용. 예: bool x : 1;은 범위가 [0, 1]이고, 이 타입으로/부터의 암시적 변환은 불리언 변환 규칙을 따라요. (C99부터)
  • 비트 정밀 정수 타입(bit-precise integer types) — 예: _BitInt(5) : 4;는 범위가 [-8, 7], unsigned _BitInt(5) : 4;는 범위가 [0, 15]예요. (C23부터)

추가로 구현 정의 타입도 허용될 수 있어요. 비트 필드가 원자(atomic) 타입을 가질 수 있는지도 구현 정의예요. (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
}

여러 인접한 비트 필드는 묶여서(packed) 배치될 수 있고, 보통 그렇게 돼요.

#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
}

특별한 경우로, 폭 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부터)와 함께 쓸 수 없어요. (C11부터)

주의할 점(Notes)

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

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

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

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

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

  • int 타입의 비트 필드를 부호 있는 것으로 취급할지 부호 없는 것으로 취급할지.
  • int, signed int, unsigned int, _Bool(C99부터), (그리고 unsigned일 수도 있는) _BitInt(N)(C23부터) 이외의 타입이 허용되는지 여부.
  • 원자 타입이 허용되는지 여부. (C11부터)
  • 비트 필드가 할당 단위 경계를 걸쳐(straddle) 차지할 수 있는지 여부.
  • 할당 단위 안에서 비트 필드의 순서(어떤 플랫폼에서는 비트 필드가 왼쪽에서 오른쪽으로, 또 어떤 곳에서는 오른쪽에서 왼쪽으로 묶여요).

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

C++ 프로그래밍 언어에서는 비트 필드의 폭이 기반 타입의 폭을 넘을 수 있는데(그러면 남는 비트는 패딩 비트예요), int 타입 비트 필드는 항상 부호 있는 것으로 취급돼요.

더 알아보기

  • struct/union: 비트 필드가 선언되는 문맥.
  • 메모리 정렬(alignment): 객체가 메모리에 배치되는 규칙.