[필드와 레코드]

데이터가 의미를 갖는 최소단위를 필드라고 하고, 여러 필드가 모여서 하나의 대상에 대한 자료가 된것이 레코드이다.

이러한 관점에서 구조체를 바라보자.

예. struct student { int id ; double score; } ;

위와 같이 스투던트를 나타내는 자료형을 보면, id 를 나타내기위한 4바이트의 영역과, 점수를 나타내기위한 8바이트의 영역을 합쳐, 총 12바이트짜리 데이터타입이 되는 것이다.

이때 첫 4바이트는 id 를 나타내는 필드가 되고, 두번째인 8바이트는 점수를 나타내기 위한 필드이다.
그리고, student 는 4바이트, 8바이트의 두개의 필드를 갖는 레코드라고 할 수 있다.


-----------------------------------------------------------------------------

[비트필드의 구문]

C에서는 구조체나 공용체를 통해서 사용자 정의형을 만들때, 비트 단위로 필드 폭을 셋팅할 수 있는데, 이렇게 비트 단위로 폭이 셋팅된 멤버를 비트필드(bit-field) 라고 부른다. 즉, 그 레코드의 특정 필드를 나타낸다.

비트필드를 선언하는 구문은 다음과 같다.

bit_field_member  ::= { int | unsigned }1 { identifier }opt : bit_field_width
bit_field_width  
::= nonnegative_constant_integral_expression

즉, 비트필드는 정수형 멤버만 가능하며, 폭은 음아닌 정수만 가능하다.
폭의 최대값은 워드 길이와 같다. 즉, 32 비트 컴퓨터의 경우 맥시멈 32 까지다.
컴파일러는 비트필드들을 최소의 기계 워드로 packing 한다.

----------------------------------------------------------------------------------------------

[비트필드 타입]

비트필드는 unsigned 비트필드와 int 비트필드 두가지가 있다.

unsigned 비트필드는 당연히 영아닌 음수만 저장이 가능하고, int 형 비트필드는 시스템의존적이다.

다음의 예제를 살펴보자.

예.
struct rcd {
unsigned fd1 : 2 ;           // unsigned 비트 필드, 폭 = 2비트
int fd2 : 3 ;                    // int 비트 필드, 폭 = 3비트
} ;


첫번째 비트필드 fd1 은 2비트이므로, 2의 2승 개, 즉 4개의 정보가 저장이 가능하다.
즉, 0, 1, 2, 3

두번째 비트필드 fd2 는 3비트이므로, 2의 3승 개, 즉 8개의 정보가 저장이 가능하지만, int형 이므로, 저장할 수 있는 형태는 시스템에 의존적이다. 어떤 시스템에서는 폭이 얼마건 간에 최상위 비트를 부호비트로 사용한다.

또한, 시스템마다 비트와 바이트를 워드의 상위부터 카운트 하는지, 하위부터 하운트 하는지가 다르기 때문에 비트필드를 사용한 코드를 다른 시스템에 이식할때는 주의를 요한다.

int 형 비트필드를 테스트 해보기 위해 다음과 같은 간단한 코드를 돌려본다.

예.
#include<stdio.h>
typedef struct { unsigned fd1 : 2 ; int fd2 : 3 ; } rcd;

int main(void){
     rcd a={0};           // 구조체 초기화
     int i;

     for(i=0;i<=16;i++)
          printf("a.fd1= %u \t\t a.fd2= %d\n",a.fd1++ , a.fd2++);

     return 0;
}

위의 루프는 각 멤버를 1씩 증가시키면서 배정하고, 출력한다.

아래는 나의 실행결과이다.

사용자 삽입 이미지

즉, 두번재 비트필드 3비트 중에 최상위 비트가 부호 비트로 쓰였음을 알 수 있다.

그래서 011  = 3  다음에 100 = 4 가 입력되었지만, 첫비트가 부호비트인데 오버플로우 되어서 100 = -4 로 해석된 것이다.
마찬가지로, 5 = 101 은  -3 으로, 6 = 110 은 -2 로 , 7 = 111 은  -1 이 되었다. 그리고 이후로 폭을 넘는 값은 잘려서 버려졌음을 알 수 있다.

즉, 현재 나의 환경에서는 int 비트빌드가 32비트보다 작더라도, 음수를 입력할수 있으며, 양수를 오버플로우시켜서 최상위의 부호비트를 변화시켜 음수로 해석되도록 할 수도 있다.

또한, 입력된수의 이진값이 비트필드의 폭을 넘어가는 경우에, 인접한 비트필드로는 오버플로우 되지 않음을 알 수 있다.

---------------------------------------------------------------------------------------------------

[비트필드 얼로케이션]

컴파일러가 비트들을 좌에서 우로 할당하느냐 또는 우에서 좌로 할당하느냐 하는 것은 시스템에 의존한다.
대부분의 컴퓨터에서는 비트필드가 워드 바운더리(word boundary) 에 걸치지 않도록 할당한다.

컴파일러가 비트필드를 어떻게 배치시키는가를 알아보기 위해, 각자 프로그램을 작성해 보자.
다음의 첨부파일은 내가 사용중인 컴파일러가 비트필드를 어떻게 배치시키는가 알아보려고 만들었다.


프로그래밍 팁 : 구조체의 경우는 << 나 >> 연산자로 바로 다룰수가 없다. 그래서, int 형과 함께 공용체로 묶어서 새로운 타입을 정의한후에, 구조체 데이터를 공용체에 배정하고, 공용체의 int형으로 읽어들이면, << 와 >> 로 접근 가능하다. 물론 이것말고도 방법은 여러가지다.

실행화면.
사용자 삽입 이미지


그림을 보면, 첫번째 비트필드 2비트를 최하위에 배치하고, 그다음 비트필드 3비트를 그것의 상위 3비트에 배치했음을 알 수 있다.

따라서, 하위비트부터 차례대로 비트필드를 얼로케이션한다는 것을 알 수 있다. ( 물론 이것은 나의 환경에서 그렇다는 것이다. )

----------------------------------------------------------------------------------------------------

[비트필드 정렬]

앞에서 구문을 보면, identifier 가 option 으로 되어있는 것을 볼 수가 있다. 즉, 사용하지도 않을 비트필드를 지정할 수 있는데, 이러한 이름없는 비트필드는 패딩이나 얼라인(align)을 위해 사용된다.

즉, 빈 공백을 이름없는 비트필드로 메꿔넣음으로 해서, 이후에 사용할 비트필드를 그다음 워드의 앞자리로 이동시키거나 할 때 쓰면 된다.

가령 4바이트 워드에서 다음과 같은 워드를 두개 사용하는 구조체를 고려하자.

struct abc { unsigned i1:7, i2:7, i3:7, :11,                 // 이름없는, 폭11 짜리 비트필드는
                               i4:7, i5:7, i6:7;                      //  그 다음 비트필드가 다음 워드에 오도록 한다.
                };



바로 다음워드에 정렬하는 또다른 방법은 폭이 0 인 이름없는 비트필들르 사용하는 것이다.

struct abc { unsigned i1:7, :0 , i2:7, :0, i3:7 ; };         // :0 은 다음 워드까지 빈필드로 채운다.

이것은 세개의 다른 워드에 세 개의 7비트 필드를 만들게 한다.


-------------------------------------------------------------------------------------------------

앞에서 이미 언급했듯이 구조체 뿐만 아니라 공용체에 대해서도 비트필드를 사용할 수 가 있는데, 구조체, 공용체, 그리고 비트필드를 적절히 조합하면 많은 기술들을 구현할수 있다.