관우는 왜 C 언어를 살육했나? [2-12] 비트 필드
by
[2-12] 비트 필드
비트 필드는 사용자 지정 자료형인 구조체의 구성 요소를 비트 단위로 나누는 기법입니다. 다음과 같이 선언합니다.
struct 구조체명 {
자료형 변수명: 비트수;
자료형 변수명: 비트수;
자료형 변수명: 비트수;
...
}
예를 들어 8비트를 다음과 같이 비트 필드로 나눠서 성별/학년/반을 나타낼 수 있습니다.
struct BF{
unsigned int sex: 1; // 1비트면 남성/여성을 표현할 수 있습니다.
unsigned int grade: 3; // 초등학교는 3비트면 표현할 수 있습니다.
unsigned int room: 4; // 10학급까지 있다면 4비트로 표현할 수 있습니다.
}
변수 선언 방법은 다음과 같습니다.
struct BF bitfield;
이 구조체를 그림으로 보면 다음과 같습니다.
[그림] 구조체 bitfield의 메모리 구조
비트 필드 초기화
초기화는 다음과 같이 두 가지 방법이 있습니다.
- 선언하면서 초기화하기
- 선언 후에 초기화 하기
첫 번째 방법은 구조체와 같습니다.
struct BF bitfield = {"관우", 1, 2, 3};
두 번째 방법 역시 구조체와 같습니다.
struct BF bitfield;
bitfield.sex = 1; <= 1 대입
bitfield.grade = 3; <= 3 대입
bitfield.room = 4; <= 4 대입
기존에 접한 방법이라 어렵지 않을 겁니다.
비트 필드 사용하기
비트 필드를 사용하지 않은 멤버 변수와 사용법은 완전히 같습니다. 점 연산자만 찍어주면 되죠! 사용법을 예제로 알아봅시다.
#include <stdio.h>
struct BF{
unsigned int sex: 1; // 1이면 남성, 0이면 여성
unsigned int grade: 3; // 초등학교는 3비트면 표현할 수 있습니다.
unsigned int room: 4; // 10학급까지 있다면 4비트로 표현할 수 있습니다.
};
int main(void) {
struct BF bitfield;
int s;
int g;
int r;
printf("정보를 입력하세요(sex, grade, room)\n");
scanf("%d %d %d\n", &s, &g, &r);
bitfield.sex = s;
bitfield.grade = g;
bitfield.room = r;
if(bitfield.sex){
printf("남성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
else{
printf("여성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
return 0;
}
[출력]
정보를 입력하세요(sex, grade, room)
1 2 3
남성 : 2학년 3반
[출력]
비트 필드를 사용하면 메모리 공간을 비트 단위로 다룰 수 있고 메모리 공간을 절약할 수 있습니다.
그런데 의아한 점이 하나 있군요? 예제에서 scanf()로 값을 받는 코드를 다시 살펴보겠습니다.
scanf("%d %d %d\n", &s, &g, &r);
구조체 비트 필드 멤버 변수에 직접 scanf를 사용하지 않고 s, g, c 변수를 따로 선언해 scanf()로 받은 값을 비트 필드에 대입하는군요.
왜 그랬을까요? 그 이유는 제 친구 제갈량이 설명해줄 겁니다. 구미가 당긴다면 한 번 읽어보세요.
제갈량의 동남풍 : 비트 단위로 메모리 접근하기
그리고 놀라운 하나! 비트필트는 & 연산자를 사용해 값을 할당할 수 없습니다. 따라서 다음과 같은 scanf()는 컴파일 에러를 발생합니다.
scanf("%d\n", &bitfield.sex);
& 연산자는 주소 연산을 하게 되는데 바이트 단위로 연산하기 때문입니다. 즉 비트단위로 접근을 못 합니다. 예를 들어 char형 배열의 출력을 잠깐 살펴보겠습니다.
#include <stdio.h>
int main(void) {
char test[4];
for(int i ; i < sizeof(test); i++)
{
printf("0x%x\n", &test[i]);
}
// your code goes here
return 0;
}
[결과]
0xb0227ad0
0xb0227ad1
0xb0227ad2
0xb0227ad3
char형 배열의 주소를 출력했더니, 맨 마지막 주소가 1씩, 즉 1바이트씩 변하죠? int형 배열이었다면 당연히 4씩 증가했을 겁니다. 여기서 1은 1바이트죠. 주소를 표현하는 최소 단위가 1바이트인 겁니다. 따라서 비트 단위로 주소를 접근할 방법이 애초에 없는 겁니다. 그래서 &연산을 지원하지 않습니다.
이상 제갈량이었습니다.
風
비트 필드 사용 팁팁팁
몇 가지 비트 필드를 사용하는 팁을 알려드릴게요.
- 정의와 선언을 동시에 하다.
- 바이트 단위로 나누기
- 비트 건너뛰고 할당하기
- 자료형 생략하기
- signed는 지정은 무의미해
정의와 선언 동시에 하기
구초제와 마찬가지로 정의와 선언을 동시에 할 수도 있다.
struct BF{
unsigned int sex: 1;
unsigned int grade: 3;
unsigned int room: 4;
} bitfield; <= 구조체 BF형 변수 bitfield 선언
바이트 단위로 나누기
요소의 비트 크기를 0으로 하면 해당 위치에서 바이트를 나누라는 의미입니다.
struct BF{
unsigned int sex: 1;
unsigned int contry: 5;
unsigned int boundary: 0; <= 여기가 바이트 경계
unsigned int grade: 3;
unsigned int room: 4;
};
왜 바이트 나누기를 할까요? 메모리 주소는 바이트 단위로 접근할 수 있다고 했죠? 위에서 구역을 나누지 않으면 grade가 첫 번째 바이트와 두 번째 사이에 걸치게 되죠. 그럼 grade를 읽을 때는 주소에 두 번 접근을 해야 합니다. 따라서 어차피 2바이트 사용한다면 구역을 나눠서 두 번째 시작 위치부터 grade를 할당하는 게 속도 향상에 도움이 되죠.
이때 다음과 같이 경계를 나누는데 사용하는 비트 필드 이름을 생략할 수 있어요.
struct BF{
unsigned int sex: 1;
unsigned int contry: 5;
unsigned int : 0; <= 바이트 경계 변수명 생략
unsigned int grade: 3;
unsigned int room: 4;
};
왜 바이트 나누기를 할까요? 메모리 주소는 바이트 단위로 접근할 수 있다고 했죠? 위에서 구역을 나누지 않으면 grade가 첫 번째 바이트와 두 번째 사이에 걸치게 되죠. 그럼 grade를 읽을 때는 주소에 두 번 접근을 해야 합니다. 따라서 어차피 2바이트 사용한다면 구역을 나눠서 두 번째 시작 위치부터 grade를 할당하는 게 속도 향상에 도움이 되죠.
비트 건너뛰고 할당하기
건너뛸 비트 수를 명시할 수도 있습니다. 비트 필드 변수명 없이 건너뛸 비트 수만 적어주면 됩니다.
struct BF{
unsigned int sex: 1;
unsigned int : 5; <= 건너뛸 비트 수 지정
unsigned int grade: 3;
unsigned int room: 4;
};
자료형 생략하기
비트 필드에서는 다음과 같이 자료형을 생략할 수 있습니다.
struct BF{
unsigned sex: 1;
unsigned boundary: 0;
unsigned grade: 3;
unsigned room: 4;
};
그렇다고 변수명과 비트 크기만 쓰는 방식을 지원하지는 않습니다.
struct BF{
sex: 1; // 컴파일 에러 발생
grade: 3; // 컴파일 에러 발생
room: 4; // 컴파일 에러 발생
};
signed는 지정은 무의미해
비트 필드에서도 signed를 사용할 수 있습니다. 하지만 어차피 빅엔디안 방식으로 순차적으로 비트가 할당되므로 큰 의미는 없습니다.
struct BF{
signed sex: 1;
signed grade: 3;
signed room: 4;
};
일반 멤버 변수와 비트 필드 쓰기
그렇다면 일반 멤버 변수화 비트 필드 변수를 같이 쓸 수 있을까요? 물론 같이 쓸 수 있습니다. 사용 법은 기존과 같아서 다를게 없지만, 참고 삼을 수 있게 간단한 예제를 제공해드릴게요.
#include <stdio.h>
struct BF{
char name[20];
unsigned int sex: 1; // 1이면 남성, 0이면 여성
unsigned int grade: 3; // 초등학교는 3비트면 표현할 수 있습니다.
unsigned int room: 4; // 10학급까지 있다면 4비트로 표현할 수 있습니다.
};
int main(void) {
struct BF bitfield = {"관우", 1, 2, 3};
if(bitfield.sex){
printf("남성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
else{
printf("여성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
return 0;
}
[결과]
남성 : 2학년 3반
비트 필드 변수가 아닌 name이 비트 필드 변수와 같은 구조체에 있습니다. 일반 구조체 때와 같은 방식으로 사용하면 되는군요!
공용체에서 비트 필드 활용하기
공용체에서도 비트 필드를 사용할 수 있습니다.
어떻게?
이름 없는 구조체를 사용해서요!
#include <stdio.h>
union BF{
int age;
struct {
unsigned int sex: 1; // 1이면 남성, 0이면 여성
unsigned int grade: 3; // 초등학교는 3비트면 표현할 수 있습니다.
unsigned int room: 4; // 10학급까지 있다면 4비트로 표현할 수 있습니다.
};
};
int main(void) {
union BF bitfield;
bitfield.sex = 1;
bitfield.grade = 2;
bitfield.room = 3;
if(bitfield.sex){
printf("남성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
else{
printf("여성 : %d학년 %d반", bitfield.grade, bitfield.room);
}
return 0;
}
[결과]
남성 : 2학년 3반
공용체 안의 구조체를 보시면 이름이 없습니다. 이미 공용체를 다루면서 이름 없는 공용체를 구조체 안에 사용한다는 걸 배웠습니다. 그 반대니까 어렵지 않을 겁니다.
이상 관우였습니다.
殺 |
Subscribe via RSS