49. 구조체 비트 필드
1. 키워드
- 구조체 비트 필드(Bit Field)
2. 구조체 비트 필드 사용하기
- 지금까지 구조체의 멤버는 각 자료형 크기만큼 공간을 차지했다.
- 하지만 구조체 비트 필드를 사용하면 구조체 멤버를 비트 단위로 저장할 수 있다.
- 특히 CPU나 기타 칩의 플래그를 다루는 저수준(Low Level) 프로그래밍을 할 때 기본 자료형보다 더 작은 비트 단위로 값을 가져오거나 저장하는 경우가 많으므로 구조체 비트 필드가 유용하게 사용된다.
3. 구조체 비트 필드를 만들고 사용하기
- C99 표준에서는 비트 필드로 사용할 수 있는 자료형을
_Bool
,signed int
,unsigned int
,int
타입으로 규정하고 있지만, 대부분의 컴파일러에서는 모든 정수 자료형을 사용할 수 있다. - 보통은 비트 필드에 부호 없는(
unsigned
) 자료형을 주로 사용한다. - 단, 실수 자료형은 비트 필드로 사용할 수 없다.
- 비트 필드는 다음과 같이 멤버를 선언할 때
:
(콜론) 뒤에 비트 수를 지정해 주면 된다.
- 이제 구조체를 7비트, 3비트, 1비트로 나눠서 비트 필드를 정의해 보자.
#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
가 최하위 비트에 오고 나머지 멤버들은 각각 상위 비트에 배치된다.
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
가 나온다.
비트 필드 비트 수 제한
- 다음과 같이 비트 필드의 멤버를 선언하는 자료형보다 큰 비트 수는 지정할 수 없다.
4. 비트 필드와 공용체를 함께 사용하기
- 보통 사람이 코드에서 값을 지정할 때는 비트 필드를 사용하지만 CPU나 칩에 값을 설정할 때는 모든 비트를 묶어서 한꺼번에 저장한다.
- 이번에는 비트 필드의 값을 한꺼번에 사용할 수 있도록 비트 필드와 공용체를 함께 사용해 보자.
#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
- 공용체로 감싸준 멤버
e
를printf
로 출력해 보면64020
이 나온다. - 즉, 비트 필드에 할당한 비트들을 차례대로 연결하면
1111 1010000 10 100
이 되므로 10진수로 표현했을 때64020
이 된다.