이번 장에서는 C 언어의 비트 필드(Bit Fields)가 무엇인지, 그리고 이를 활용해 메모리를 효율적으로 사용하는 방법에 대해 알아보겠습니다.
비트 필드가 필요한 이유
예를 들어 C 프로그램에서 TRUE/FALSE 값을 저장하는 여러 변수를 status라는 구조체로 묶어 관리한다고 가정해 봅시다.
struct {
unsigned int widthValidated;
unsigned int heightValidated;
} status;이 구조체는 8바이트의 메모리 공간을 차지합니다. 그런데 실제로 각 변수에는 0 또는 1만 저장할 예정입니다. 단 1비트만 있으면 충분한 데이터를 위해 32비트씩 낭비되는 셈이죠.
이런 상황에서 C 언어는 메모리 공간을 더 효율적으로 활용할 수 있는 방법을 제공합니다. 바로 비트 필드입니다.
비트 필드 정의하기
구조체 내부에서 변수를 선언할 때 비트 폭(width)을 지정하면, C 컴파일러에게 해당 변수가 지정한 비트 수만 사용한다고 알려줄 수 있습니다. 앞서 살펴본 구조체를 비트 필드를 사용해 다시 작성하면 다음과 같습니다.
struct {
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;이렇게 수정된 구조체는 status 변수를 위해 4바이트의 메모리를 차지하지만, 실제 값 저장에는 2비트만 사용됩니다.
흥미로운 점은, 1비트 폭의 변수를 최대 32개까지 사용해도 status 구조체는 여전히 4바이트만 사용한다는 것입니다. 하지만 33번째 변수가 추가되는 순간 다음 메모리 슬롯이 할당되어 8바이트를 사용하게 됩니다.
메모리 크기 비교 예제
다음 예제 코드를 통해 일반 구조체와 비트 필드 구조체의 메모리 크기 차이를 직접 확인해 보겠습니다.
#include <stdio.h>
#include <string.h>
/* 일반 구조체 정의 */
struct {
unsigned int widthValidated;
unsigned int heightValidated;
} status1;
/* 비트 필드를 사용한 구조체 정의 */
struct {
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status2;
int main( ) {
printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
return 0;
}실행 결과
Memory size occupied by status1 : 8 Memory size occupied by status2 : 4
일반 구조체는 8바이트를 사용하는 반면, 비트 필드를 적용한 구조체는 절반인 4바이트만 사용하는 것을 확인할 수 있습니다.
비트 필드 선언 문법
구조체 내에서 비트 필드는 다음과 같은 형식으로 선언합니다.
struct {
type [member_name] : width ;
};각 요소의 의미는 다음 표와 같습니다.
| 요소 | 설명 |
|---|---|
| type | 비트 필드 값이 해석되는 방식을 결정하는 정수형 타입입니다. int, signed int, unsigned int 중 하나를 사용할 수 있습니다. |
| member_name | 비트 필드의 이름입니다. |
| width | 비트 필드의 비트 수입니다. 지정한 타입의 비트 폭보다 작거나 같아야 합니다. |
여러 비트를 사용하는 비트 필드
비트 필드는 반드시 1비트일 필요는 없습니다. 미리 정의된 폭으로 선언된 변수를 비트 필드라고 부르며, 하나의 비트 필드는 여러 비트를 담을 수도 있습니다.
예를 들어 0부터 7까지의 값을 저장해야 하는 변수가 필요하다면, 3비트 폭의 비트 필드를 다음과 같이 정의할 수 있습니다.
struct {
unsigned int age : 3;
} Age;위 구조체 정의는 C 컴파일러에게 age 변수가 값을 저장하는 데 3비트만 사용한다고 알려줍니다. 만약 3비트를 초과하는 값을 저장しよう 하면 허용되지 않습니다. 다음 예제로 직접 확인해 보겠습니다.
비트 필드 값 범위 테스트 예제
#include <stdio.h>
#include <string.h>
struct {
unsigned int age : 3;
} Age;
int main( ) {
Age.age = 4;
printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
printf( "Age.age : %d\n", Age.age );
Age.age = 7;
printf( "Age.age : %d\n", Age.age );
Age.age = 8;
printf( "Age.age : %d\n", Age.age );
return 0;
}실행 결과
Sizeof( Age ) : 4 Age.age : 4 Age.age : 7 Age.age : 0
실행 결과 분석
결과를 자세히 살펴보면 흥미로운 동작을 발견할 수 있습니다.
- 4와 7은 정상적으로 저장됩니다. 3비트로 표현 가능한 최대값이 7(2³ − 1 = 7)이기 때문입니다.
- 8을 저장하려 하면 0이 됩니다. 8은 3비트로표현할 수 없는 값이므로, 하위 3비트만 남기고 나머지는 버려져 결과적으로 0이 저장됩니다.
즉, 비트 필드 폭을 초과하는 값을 대입하면 컴파일 오류가 발생하지 않고, 값이 잘려나간(truncated) 형태로 저장됩니다. 따라서 비트 필드를 사용할 때는 저장하려는 값의 범위를 항상 염두에 두어야 합니다.
마무리
비트 필드는 임베디드 시스템이나 하드웨어 제어 프로그래밍처럼 메모리가 제한적인 환경에서 특히 유용합니다. 플래그(flag) 변수들이 많거나, 특정 비트 수로 표현되는 하드웨어 레지스터를 다룰 때 비트 필드를 활용하면 코드의 가독성을 높이면서 메모리 사용량까지 줄일 수 있습니다.