설 명절날 시골에 내려와 오늘은 어떤 글을 올릴까 고민을 하다가 비트필드 구조체에 대해 적어보기로 했다.
(PC로 코드 검증이 필요 없는 것을 고민하다 나온 결론)
IoT 또는 통신 분야 관련된 Firmware를 개발하다보면 data frame이라는 단어를 많이 마주치게 된다.
위의 그림은 BLE data frame중 하나이다.
위 그림은 많은 통신 분야 또는 chip register를 컨트롤할 때 많이 보게 되는데, 일정한 용량 안에 최대한 많은 데이터 의미를 넣기 때문에 bit 단위로 의미를 부여하게 된다.
예를 들어 위 그림에서 LL Ovhd를 코드로 해석할 때 아래와 같은 부분을 사용할 수 있다.
1
2
3
4
5
6
7
|
int main(void){
unsigned short length;
unsigned short channelId;
unsigned int LLOvhd = 0;
LLOvhd = (length << 16) | channelId;
return 0;
}
|
cs |
위 코드로도 LLOvhd를 해석하는데는 전혀 문제 없지만, 값을 저장하거나 읽을 때마다 변수를 선언하고 쉬프트 연산자를 사용하는 등의 번거로운 과정이 계속 발생하게 된다.
이를 파워풀하게 해결할 수 있는 방법이 바로 비트필드 구조체이다.
1
2
3
4
5
6
7
8
9
10
11
|
typedef struct{
unsigned int length:16;
unsigned int channedlId:16;
}LLOVHD_T;
int main(void){
LLOVHD_T llovhd;
llovhd.length = 10;
llovhd.channedid = 24;
return 0;
}
|
cs |
위의 코드를 보면 LLOVHD_T라는 구조체를 선언하였고 그 안에 unsigned int 형태의 length, channedId가 있다.
자세히 보면 옆에 :16이라고 적혀 있는 부분이 있는데, 이것이 바로 비트필드 형식이다.
unsigned int는 32bit형태이므로 length 16bit, channelId 16bit로 총 32bit로 할당되어 LLOVHD_T의 사이즈는 4byte(32bit)이다.
프레임에 연결할때는 간편하게 llovhd를 사용하여 4byte단위로 구성토록 하고, 값을 세팅하거나 읽을 때는 비트필드 구조체 형태로 접근하여 정해진 bit로 나누어 읽을 수 있다.
사용시 주의점
사용시 주의점은 아래와 같다.
1. 선언된 변수(ex. unsigned int)의 메모리 크기를 넘어서면 하나의 변수가 추가된다.
1
2
3
4
5
6
7
8
9
10
11
|
typedef struct{
unsigned int length:24;
unsigned int channedlId:16;
}LLOVHD_T;
int main(void){
LLOVHD_T llovhd;
llovhd.length = 10;
llovhd.channedid = 24;
return 0;
}
|
cs |
코드를 약간 바꿔 length에 24bit를 할당하였다. 다음 channelId가 16bit가 필요하고, unsigned int 1개의 변수에서 24bit를 이미 사용하였으므로 channelId를 위한 unsigned int 변수 하나가 추가되어 LLOVHD_T는 8byte크기의 구조체로 결정된다.
2. 사용하지 않는 영역에 대해서는 이름 없이 bit수만 넣어도 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
typedef struct{
unsigned char stx:2;
unsigned char data:2;
unsigned char revd:2;
unsigned char etx:2;
}DATA;
typedef struct{
unsigned char stx:2;
unsigned char data:2;
unsigned char :2;
unsigned char etx:2;
}DATA;
|
cs |
예시를 위해 1byte frame을 만들어 보았다. 위에서 revd는 사용하지 않는 영역으로 향후 data 확장시 여유 공간으로 남겨놓은 부분이다. 첫 번째 구조체처럼 revd를 명시하는 경우 이름으로 뜻을 파악하기는 좋으나 불필요한 접근 또는 데이터 저장으로 개발자의 오류를 야기시킬 가능성이 존재한다. 이러한 리스크를 제거하기 위해 아래 방식처럼 이름을 없애는 경우 해당 공간은 이름이 없기 때문에 접근이 불가능하여 불필요한 오류를 줄일 수 있다.
3. 활용방법 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
typedef struct{
unsigned char stx:2;
unsigned char data:2;
unsigned char :2;
unsigned char etx:2;
}DATA;
unsigned char data = 0xCE;
int main(void){
DATA* d = (DATA*)data;
printf("data value : %x\n", d->data);
return 0;
}
|
cs |
비트필드 구조체를 제대로 활용하는 방법은 위와 같이 data frame을 보고 구조체를 설계한 뒤 통신을 통해 들어온 data를 구조체 포인터 형태로 재해석하여 data에 접근하는 것이다. 번거로운 변환 과정이 가장 적고 가독성이 좋아 실수를 줄이고 유지보수하기 아주 좋으니 실무에 반드시 활용해보자.
모두 새해복 많이 받길 바라며 이 글을 마친다.
'C언어' 카테고리의 다른 글
[C 언어] Dangling pointer (0) | 2023.01.28 |
---|---|
[C 언어] 연산자 우선순위 (0) | 2023.01.26 |
[C 언어] 데이터 타입의 형 변환 (2) (0) | 2023.01.20 |
[C 언어] 데이터 타입 2번째 (0) | 2023.01.14 |
[C 언어] [배열] 배열 기초 (0) | 2023.01.13 |