C 언어 | 구조체 선언 | 비트 필드
구조체의 여러 멤버를 미세하게 비트 단위로 분할하여 사용하는 방법을 설명한다. 비트 단위에 분리된 여러 정보를 하나의 구조로 패키지화할 수 있다.
비트 단위의 분할
비트 필드에는 구조체의 멤버에 관한 기능에 여러 값을 미세하게 비트 단위로 분리해 사용하는 경우에 유용하다.
예를 들어, 전자 음악 규약의 MIDI(Musical Instrumunt Digital Interface)이다. 이 프로토콜은 1980 년대 초반의 8bit CPU 시대의 산물로, 지금도 변함없이 전자 악기 분야에서 이용되고 있다. 당시의 프로세서는 지금보다 훨씬 느리고, 메모리도 지금과는 비교할 수 없을 정도로 용량이 적고 고가였다. 당시는 어떻게 정보를 효율적으로 전달하고, 적은 메모리 공간에서 더 빠르게 처리할 것인가하는 문제가 프로그램의 간단함과 구조의 아름다움을 무시하게 되었다. 그 수단으로 바이트에 비트 단위의 데이터를 분할하여 저장하는 방법을 생각할 수 있다.
비트 필드는 2개의 정보를 4비트씩 분할하여 1바이트로 압축하여 전송하는 처리에 사용할 수 있다. 이것은 처리계에 의존하는 수치형의 실체를 필드는 비트 단위로 분할하여 사용하는 것이다. 따라서 4비트와 6비트의 멤버를 만들 수 있게 된다. 다만, 비트 필드는 주소가 존재하지 않기 때문에 포인터로 사용할 수 없다.
비트 필드를 이용하려면 구조체의 선언으로 멤버 이름 뒤에 콜론":“과 필드의 비트 길이를 지정한다.
struct Msg {
unsigned int type : 1;
unsigned int attr : 3;
unsigned int id : 4;
};
이 Msg 구조체는 1비트로 구성되는 type, 3비트로 구성 attr, 4비트로 구성 id 멤버를 가지고 있다. 부호없는 정수임을 강조하기 위해 일반적으로 비트 필드의 선언은 unsigned int를 사용한다.
다만, 비트 필드를 가진 구조체의 인스턴스가 어떤 메모리 구조가 될지는 구현에 크게 의존한다. 바이트 단위를 걸쳐 비트 필드의 구조가 메모리 공간에서 어떻게 표현되는지에 대해 C 언어는 정해져 있지 않다. 처리 단위의 상위 비트에서 필드를 할당할지도 모르고, 하위 비트에서 할당될 수 있다.
코드1
#include <stdio.h>
struct MidiMsg {
unsigned int status : 4;
unsigned int channel : 4;
unsigned int second : 8;
unsigned int third : 8;
};
int main() {
struct MidiMsg msg = { 9 , 0 , 0x3C , 40 };
printf(
"status = %d\nchannel = %d\n"
"second = %d\third = %d\n" ,
msg.status , msg.channel , msg.second , msg.third
);
return 0;
}
코드1은 비트 필드를 가진 MidiMsg 구조체를 선언하고 있다. 이 구조체는 MIDI 하드웨어에 전달하는 메시지를 나타낸다. MIDI에 대한 지식이 필요하지 않는다. MIDI 메세지의 status 그리고 channel 정보가 4비트 단위로 분할되어 있다는 것에 주목하자. 예를 들어, 이 구조는 32비트 컴퓨터에서는 다음과 같은 구성이 될 것으로 예상된다.
표1 - 32비트 컴퓨터에서 MidiMsg 구조체의 구조
unsigned int 32 bit |
---|
status 4 bit | channel 4 bit | second 8 bit | third 8 bit | 사용하지 않는 영역8 bit |
반복이 되지만, 비트 필드가 어떻게 인스턴스화 되는지는 구현에 따라 달라진다. 그러나 실행 결과는 예상대로라고 생각한다. 비트 필드가 그 비트 단위로 제대로 분해되어 있는지 확인하고 싶다면, status 멤버에 0xF 이상의 값을 대입해 보면 알 수 있다. status 멤버는 4비트이므로, 그 이상의 값을 대입하면 상위 비트가 잘린다.