C 구조체 비트필드 - sonkoni/Koni-Wiki GitHub Wiki

구조체 비트필드

구조체 비트필드는 구조체 멤버의 각 자료형 크기를 비트 단위로 나누어서 사용하는 것을 말한다.

struct 구조체이름 {
    정수형자료형 멤버이름 : 비트수;
}
  • 모든 정수형에서 사용할 수는 있지만, 주로 _Bool이나 unsigned 정수 자료형에서 부호없이 사용한다.
  • 비트필드를 먹인 멤버는 오프셋 열람(offsetof)이 안 된다.
  • 비트 필드 각 멤버가 선언 순서대로 저장될지, 최하위비트부터 저장될지는 unspecified 이며 컴파일러마다 다르다
    일반적으로 오른쪽(Least Significant Bit, LSB, 최하위비트)부터 채워 나간다.
  • (중요) 1 ~ 자료형크기 까지 비트수 입력 가능. 이상은 사용불가.
    예, unsigned int 는 32비트. 즉, 1부터 32까지 입력 가능
  • (중요) 비트수를 초과하는 부분의 비트는 버려진다.
  • (중요) 자료형이 섞여있을 경우 전체 크기는 가장 큰 자료형의 크기를 따른다.
struct Flags {
    unsigned short a : 1;
    unsigned short b : 4;
    unsigned short c : 8;
};

//           1byte                   1byte                   
//  ┌────────────────────┐  ┌────────────────────┐ 
// 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0   Bit order
// [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]  unsigned short 크기
//  -------  ======================  ==========  =
//    (x)              c                 b       a
//  사용안함            8비트              4비트     1비트
// 0으로 채워짐
//
// 구조체 총 크기 : 2바이트
// 총 16비트 중 13비트만 사용한다.

지정된 비트수 초과분은 짤린다.

#include <stdio.h>
#include <limits.h>

struct Flags {
    unsigned short a : 1;
    unsigned short b : 4;
    unsigned short c : 8;
};

int main(int argc, char *argv[]) {
    struct Flags f1;
    
    f1.a = 0b1;         // 0000 0001 : 1
    f1.b = 0b11111;     // 0001 1111 : 31    (!)비트초과 컴파일러 경고
    f1.c = 0b11111111;  // 1111 1111 : 255
    
    for (int i = CHAR_BIT - 1; i >= 0; i--) { 
        printf("%c", (f1.a >> i & 1) + '0');
    }
    printf(":: %d\n", f1.a);
    for (int i = CHAR_BIT - 1; i >= 0; i--) { 
        printf("%c", (f1.b >> i & 1) + '0');
    }
    printf(":: %d\n", f1.b);
    for (int i = CHAR_BIT - 1; i >= 0; i--) { 
        printf("%c", (f1.c >> i & 1) + '0');
    }
    printf(":: %d\n", f1.c);

    printf("==> %zu\n", sizeof(f1));
    
    return 0;
}
// 00000001:: 1
// 00001111:: 15     비트 하나가 짤려서 1111 이 됐다.
// 11111111:: 255
// ==> 2

다른 타입이 섞여 있는 경우

다른 타입이 섞여 있어도 상관없다. 전체 필드 크기는 가장 큰 자료형에 맞춰 비트를 분할한다.

struct Option {
    unsigned char  flag1 : 1;
    unsigned short flag2 : 1;   
};
// ==> 구조체 총 크기: 2바이트(16비트)
// 총 16비트 중 2비트만 사용한다.

비트의 합이 데이터 타입보타 큰 경우

가장 큰 자료형의 배수로 증가한다.

struct Option {
    unsigned char  flag1 : 8;
    unsigned short flag2 : 16; 
};
// ==> 구조체 총 크기: 4바이트(32비트)
// 총 32비트 중 24비트만 사용한다.

이럴 때 쓴다: 구조체로 비트 플래그를 만들 때

구조체로 값을 만들어 편하게 넣고, 이것을 일련의 비트 자료형으로 만든다.

아래 예시에서 비트가 집합된 ipaddr 은 비트 스위치로 활용하기 좋다.

#include <stdio.h>
#include <string.h>  /* memcpy */

// unsigned int == 32비트
struct IPv4_Addr {
    unsigned int addr4 : 8;
    unsigned int addr3 : 8;
    unsigned int addr2 : 8;
    unsigned int addr1 : 8;
};

int main(int argc, char *argv[]) {
    unsigned int ipaddr;
    struct IPv4_Addr addr = {0,}; // 반드시 초기화할 필요는 없다.
    
    // ip주소가 127.0.0.1 일 경우
    addr.addr1 = 127;   // 0111 1111
    addr.addr2 = 0;     // 0000 0000
    addr.addr3 = 0;     // 0000 0000
    addr.addr4 = 1;     // 0000 0001
    
    memcpy(&ipaddr, &addr, sizeof(unsigned int));
    
    for (int i = (sizeof(unsigned int) * 8) - 1; i >= 0; i--) { 
        printf("%c", (ipaddr >> i & 1) + '0');
    }

    return 0;
}
// 01111111000000000000000000000001

이름없는 구조체 이름없는 공용체 함께 써보기

이름없는 친구들을 이용하면 비트 집합을 더 쉽게 뺄 수 있다.

#include <stdio.h>

struct IPv4_Addr {
    union {
        struct {              // unsigned int == 32비트
            unsigned int addr4 : 8;
            unsigned int addr3 : 8;
            unsigned int addr2 : 8;
            unsigned int addr1 : 8;
        };
        unsigned int option;  // 윗줄의 이름없는 구조체와 영역을 공유한다.
    };
};

int main(int argc, char *argv[]) {
    struct IPv4_Addr addr = {0,}; // 반드시 초기화할 필요는 없다.
    
    // ip주소가 127.0.0.1 일 경우
    addr.addr1 = 127;   // 0111 1111
    addr.addr2 = 0;     // 0000 0000
    addr.addr3 = 0;     // 0000 0000
    addr.addr4 = 1;     // 0000 0001
    
    for (int i = (sizeof(unsigned int) * 8) - 1; i >= 0; i--) { 
        printf("%c", (addr.option >> i & 1) + '0');
    }

    return 0;
}
// 01111111000000000000000000000001

예전에는 이렇게 썼다

조합형 한글

typedef struct {
    unsigned short end : 5;     // 종성
    unsigned short mid : 5;     // 중성
    unsigned short first : 5;   // 초성
    unsigned short han_yn : 1;  // 한글비트 1이면 한글, 그렇지 않으면 한글 아님
};

초/중/종성과 한글여부 bit로 구성된 2바이트 한글로 사용되었음.

바이오스 하드웨어 제어

CPU의 register 메모리 AH, AL, AX 등의 표현을 쉽게 설정하고 이 값들을 BIOS 인터럽트 번호로 전달하여 하드웨어 제어를 하는 초창기의 16비트 PC

union AX {
    unsigned short ax;             // AX register의 전체 크기
    struct {
        unsigned short al : 8;     // register의 하위 바이트
        unsigned short ah : 8;     // register의 상위 바이트
    };
};

64비트 Double 자료형

#include <stdio.h>
#include <string.h>

struct Double {
    unsigned long mant : 52;
    unsigned long exp  : 11;
    unsigned long sign : 1;
} Double;

int main(int argc, char *argv[]) {
    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;
}

// sign = 1
// exp  = 1073
// mant = 434671933123328

double형의 데이터에 대해서 sign, 지수부, 가수부의 데이터가 어떤형식으로 저장되어 있는 지 알아볼 수 있다. 위의 s.sign의 경우는 1비트 값을 가지므로 0 또는 1만 대입할 수 있으며, 그 이외의 데이터를 저장하려고 하위 1비트 이외의 값은 버려진다.

⚠️ **GitHub.com Fallback** ⚠️