구조체의 기능 중에서 많이 사용하지 않지만, 정수형 데이터를 비트 단위로 나누어서 사용할 수 있는 기능을 제공합니다. 이를 구조체의 bit field라고 합니다.
struct 구조체명
{
정수형 멤버명1 : 비트수;
정수형 멤버명2 : 비트수;
...
};
위의 정수형은 char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long형이 올 수 있습니다.
예). 파일 접근 권한
struct file_access
{
unsigned short other : 3; // rwx
unsigned short group : 3; // rwx
unsigned short owner : 3; // rwx
unsigned short type : 3;
unsigned short egroup : 1;
unsigned short euser : 1;
};
위의 bit field 구조체는 총 14bit의 데이터를 관리할 수 있는 형태가 된다. 이 경우 구조체의 크기는 unsigned short 타입으로 2바이트 크기를 갖게 된다.
예). 다른 타입이 섞여 있는 경우
struct option
{
unsigned char flag1 : 1;
unsigned short flag2 : 1;
};
위와 같이 field의 데이터 타입이 다르게 표현하여도 상관이 없으나, 구조체의 크기는 가장 큰 타입의 크기를 따르므로 2바이트를 갖게 됩니다.
예). member의 bit의 합이 데이터 타입보타 큰 경우
struct option
{
unsigned char opt1 : 3;
unsigned char opt2 : 6;
};
위의 같이 field의 데이터 타입이 8bit로 1바이트인데, 전체 bit수가 8보다 크게 되면, 오류가 발생하는 것이 아니라 그 데이터 타입의 크기만큼 증가하여 2바이트를 차지하게 됩니다.
Bit Field의 응용
만약, IPv4의 주소를 저장하는 것을 표현한다면
#include <string.h>
struct IPv4_Addr
{
unsigned int addr4 : 8;
unsigned int addr3 : 8;
unsigned int addr2 : 8;
unsigned int addr1 : 8;
};
int main()
{
unsigned int ipaddr;
struct IPv4_Addr addr;
// ip 127.0.0.1
addr.addr1 = 127;
addr.addr2 = 0;
addr.addr3 = 0;
addr.addr4 = 1;
memcpy(&ipaddr, &addr, sizeof(unsigned int));
}
형식으로 표현할 수 있으며, addr1 ~ addr4까지는 각각 8bit씩 차지하므로 전체 크기는 4바이트를 차지하는 구조체가 됩니다. 먼저 선언한 변수가 하위 bit 부분이 됩니다. 그리고 이것을 unsigned int 변수에 복사를 하면 bit 연산자로 하지 않고 쉽게 처리할 수 있습니다.
예전에 조합형 한글을 표현하는 방식으로 이 bit field를 사용한 적이 있었습니다.
이를 위하여
typedef struct
{
unsigned short end : 5; // 종성
unsigned short mid : 5; // 중성
unsigned short first : 5; // 초성
unsigned short han_yn : 1; // 한글비트 1이면 한글, 그렇지 않으면 한글 아님
};
위와 같이 초/중/종성과 한글여부 bit로 구성된 2바이트 한글로 사용되었습니다. 이를 통하여 한글의 제자원리를 완벽하게 지원하는 형태였는 데, 요즘은 모두 완성형의 형태로 사용하고 있죠.
또한, 공용체(union)과 더불어 CPU의 register 메모리 AH, AL, AX 등의 표현을 쉽게 설정하고 이 값들을 BIOS 인터럽트 번호로 전달하여 하드웨어 제어를 하는 초창기의 PC에서 많이 사용되었습니다. (16bit 컴퓨터 시절...)
union AX
{
unsigned short ax; // AX register의 전체 크기
struct {
unsigned short al : 8; // register의 하위 바이트
unsigned short ah : 8; // register의 상위 바이트
};
};
부동소숫점을 표현하기 위해서 double이라는 변수를 사용합니다. 이들은 만약 구현을 해야 한다면 64비트 크기의 데이터를 비트 단위로 영역을 나누어서 그 의미를 부여하고 있으므로 과도한 비트 연산을 진행해야할 것입니다.
#include <stdio.h>
#include <string.h>
struct Double
{
unsigned long mant : 52;
unsigned long exp : 11;
unsigned long sign : 1;
} Double;
int main(void)
{
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;
}
위와 같이 double형의 데이터에 대해서 sign, 지수부, 가수부의 데이터가 어떤형식으로 저장되어 있는 지 알아볼 수 있습니다. 위의 s.sign의 경우는 1비트 값을 가지므로 0 또는 1만 대입할 수 있으며, 그 이외의 데이터를 저장하려고 하위 1비트 이외의 값은 버려집니다.
'C언어 > 문법' 카테고리의 다른 글
29. 구조체(struct) - 구조체의 크기와 멤버 변수의 메모리 위치 (0) | 2019.12.05 |
---|---|
28. 구조체(struct) - 구조체 내의 구조체 (0) | 2019.12.05 |
26. 구조체(struct) - 기초 (0) | 2019.12.04 |
25. 포인터(Pointer) - 함수 포인터 (2) | 2019.12.02 |
24. 포인터(Pointer) - void * 포인터 (0) | 2019.12.02 |