C 구조체 비트필드 - sonkoni/Koni-Wiki GitHub Wiki
구조체 비트필드는 구조체 멤버의 각 자료형 크기를 비트 단위로 나누어서 사용하는 것을 말한다.
struct 구조체이름 {
정수형자료형 멤버이름 : 비트수;
}
- 모든 정수형에서 사용할 수는 있지만, 주로
_Bool이나unsigned 정수자료형에서 부호없이 사용한다. - 비트필드를 먹인 멤버는 오프셋 열람(offsetof)이 안 된다.
- 비트 필드 각 멤버가 선언 순서대로 저장될지, 최하위비트부터 저장될지는 unspecified 이며 컴파일러마다 다르다
일반적으로 오른쪽(Least Significant Bit, LSB, 최하위비트)부터 채워 나간다. - (중요) 1 ~ 자료형크기 까지 비트수 입력 가능. 이상은 사용불가.
예, unsigned int 는 32비트. 즉, 1부터 32까지 입력 가능 - (중요) 비트수를 초과하는 부분의 비트는 버려진다.
- (중요) 자료형이 섞여있을 경우 전체 크기는 가장 큰 자료형의 크기를 따른다.
struct Flags {
unsigned short a : 1;
unsigned short b : 4;
unsigned short c : 8;
};
// 1byte 1byte
// ┌────────────────────┐ ┌────────────────────┐
// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Bit order
// [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ] unsigned short 크기
// ------- ====================== ========== =
// (x) c b a
// 사용안함 8비트 4비트 1비트
// 0으로 채워짐
//
// 구조체 총 크기 : 2바이트
// 총 16비트 중 13비트만 사용한다.#include <stdio.h>
#include <limits.h>
struct Flags {
unsigned short a : 1;
unsigned short b : 4;
unsigned short c : 8;
};
int main(int argc, char *argv[]) {
struct Flags f1;
f1.a = 0b1; // 0000 0001 : 1
f1.b = 0b11111; // 0001 1111 : 31 (!)비트초과 컴파일러 경고
f1.c = 0b11111111; // 1111 1111 : 255
for (int i = CHAR_BIT - 1; i >= 0; i--) {
printf("%c", (f1.a >> i & 1) + '0');
}
printf(":: %d\n", f1.a);
for (int i = CHAR_BIT - 1; i >= 0; i--) {
printf("%c", (f1.b >> i & 1) + '0');
}
printf(":: %d\n", f1.b);
for (int i = CHAR_BIT - 1; i >= 0; i--) {
printf("%c", (f1.c >> i & 1) + '0');
}
printf(":: %d\n", f1.c);
printf("==> %zu\n", sizeof(f1));
return 0;
}
// 00000001:: 1
// 00001111:: 15 비트 하나가 짤려서 1111 이 됐다.
// 11111111:: 255
// ==> 2다른 타입이 섞여 있어도 상관없다. 전체 필드 크기는 가장 큰 자료형에 맞춰 비트를 분할한다.
struct Option {
unsigned char flag1 : 1;
unsigned short flag2 : 1;
};
// ==> 구조체 총 크기: 2바이트(16비트)
// 총 16비트 중 2비트만 사용한다.가장 큰 자료형의 배수로 증가한다.
struct Option {
unsigned char flag1 : 8;
unsigned short flag2 : 16;
};
// ==> 구조체 총 크기: 4바이트(32비트)
// 총 32비트 중 24비트만 사용한다.구조체로 값을 만들어 편하게 넣고, 이것을 일련의 비트 자료형으로 만든다.
아래 예시에서 비트가 집합된 ipaddr 은 비트 스위치로 활용하기 좋다.
#include <stdio.h>
#include <string.h> /* memcpy */
// unsigned int == 32비트
struct IPv4_Addr {
unsigned int addr4 : 8;
unsigned int addr3 : 8;
unsigned int addr2 : 8;
unsigned int addr1 : 8;
};
int main(int argc, char *argv[]) {
unsigned int ipaddr;
struct IPv4_Addr addr = {0,}; // 반드시 초기화할 필요는 없다.
// ip주소가 127.0.0.1 일 경우
addr.addr1 = 127; // 0111 1111
addr.addr2 = 0; // 0000 0000
addr.addr3 = 0; // 0000 0000
addr.addr4 = 1; // 0000 0001
memcpy(&ipaddr, &addr, sizeof(unsigned int));
for (int i = (sizeof(unsigned int) * 8) - 1; i >= 0; i--) {
printf("%c", (ipaddr >> i & 1) + '0');
}
return 0;
}
// 01111111000000000000000000000001이름없는 친구들을 이용하면 비트 집합을 더 쉽게 뺄 수 있다.
#include <stdio.h>
struct IPv4_Addr {
union {
struct { // unsigned int == 32비트
unsigned int addr4 : 8;
unsigned int addr3 : 8;
unsigned int addr2 : 8;
unsigned int addr1 : 8;
};
unsigned int option; // 윗줄의 이름없는 구조체와 영역을 공유한다.
};
};
int main(int argc, char *argv[]) {
struct IPv4_Addr addr = {0,}; // 반드시 초기화할 필요는 없다.
// ip주소가 127.0.0.1 일 경우
addr.addr1 = 127; // 0111 1111
addr.addr2 = 0; // 0000 0000
addr.addr3 = 0; // 0000 0000
addr.addr4 = 1; // 0000 0001
for (int i = (sizeof(unsigned int) * 8) - 1; i >= 0; i--) {
printf("%c", (addr.option >> i & 1) + '0');
}
return 0;
}
// 01111111000000000000000000000001typedef struct {
unsigned short end : 5; // 종성
unsigned short mid : 5; // 중성
unsigned short first : 5; // 초성
unsigned short han_yn : 1; // 한글비트 1이면 한글, 그렇지 않으면 한글 아님
};초/중/종성과 한글여부 bit로 구성된 2바이트 한글로 사용되었음.
CPU의 register 메모리 AH, AL, AX 등의 표현을 쉽게 설정하고 이 값들을 BIOS 인터럽트 번호로 전달하여 하드웨어 제어를 하는 초창기의 16비트 PC
union AX {
unsigned short ax; // AX register의 전체 크기
struct {
unsigned short al : 8; // register의 하위 바이트
unsigned short ah : 8; // register의 상위 바이트
};
};#include <stdio.h>
#include <string.h>
struct Double {
unsigned long mant : 52;
unsigned long exp : 11;
unsigned long sign : 1;
} Double;
int main(int argc, char *argv[]) {
double d;
struct Double s;
d = -1234567890123456;
memcpy(&s, &d, 8);
printf("sign = %u\n", s.sign);
printf("exp = %u\n", s.exp);
printf("mant = %lu\n", s.mant);
return 0;
}
// sign = 1
// exp = 1073
// mant = 434671933123328double형의 데이터에 대해서 sign, 지수부, 가수부의 데이터가 어떤형식으로 저장되어 있는 지 알아볼 수 있다. 위의 s.sign의 경우는 1비트 값을 가지므로 0 또는 1만 대입할 수 있으며, 그 이외의 데이터를 저장하려고 하위 1비트 이외의 값은 버려진다.