1-6. Type Safety & Bit Fields

hyunahn·2025년 12월 4일

Type Safety & Bit Fields: 하드웨어 제어의 정석

1. The Core Concept (본질 정의)

"C언어의 int는 고무줄입니다. 하드웨어 제어에는 '강철 자'가 필요합니다."

  • 가변 크기 타입 (int, long): CPU 아키텍처(16bit, 32bit, 64bit)에 따라 크기가 변합니다. 이식성(Portability)이 없습니다.
  • 고정 크기 타입 (uint32_t 등): 어떤 환경에서도 크기가 절대 변하지 않습니다. 하드웨어 레지스터와 통신 규약(Protocol)을 다룰 때 필수입니다.
  • 비트 필드 (Bit Field): 1바이트보다 작은 단위(비트)로 구조체 멤버를 쪼개서 메모리를 할당하는 기법입니다.

2. Under the Hood (하드웨어 투시)

하드웨어 레지스터를 직접 제어하는 상황을 시각화해 보겠습니다.

A. int의 배신 (왜 stdint.h를 써야 하는가)

여러분이 32비트 PC에서 long(4바이트)을 써서 코드를 짰는데, 64비트 서버로 가져가니 long이 8바이트가 되어버렸습니다. 메모리 레이아웃이 완전히 틀어집니다.

그래서 1999년(C99 표준), 구원투수가 등장했습니다: <stdint.h>

기존 타입 (불확실)고정 크기 타입 (확실)의미
unsigned charuint8_t부호 없는 8비트 (1 Byte)
unsigned shortuint16_t부호 없는 16비트 (2 Bytes)
unsigned intuint32_t부호 없는 32비트 (4 Bytes)
unsigned long longuint64_t부호 없는 64비트 (8 Bytes)

"임베디드나 커널 코드에서는 int를 쓰지 마십시오. 루프 카운터(i) 정도에만 쓰세요. 데이터 구조체에는 무조건 uint*_t를 쓰십시오."

B. 비트 필드와 레지스터 맵핑

하드웨어의 제어 레지스터(Control Register)는 보통 32비트 안에 온갖 스위치를 욱여넣습니다.

[상황] 가상의 LED 제어 레지스터 (8비트)

  • 0번 비트: 전원 (On/Off) - 1 bit
  • 1~3번 비트: 밝기 (0~7) - 3 bits
  • 4~7번 비트: 모드 (Blink 등) - 4 bits

이걸 C언어 구조체로 표현하면 이렇게 메모리에 매핑됩니다.

struct LED_Reg {
    uint8_t power  : 1; // 1 bit 사용
    uint8_t bright : 3; // 3 bits 사용
    uint8_t mode   : 4; // 4 bits 사용
};

[메모리 내부 투시도 (1 Byte)]

       [ MSB (7) . . . . . . . . . . LSB (0) ]
Bits:  |  Mode (4)   | Bright (3)  | Power(1)|
       +-------------+-------------+---------+
Val :  |  1  0  1  0 |   1  0  1   |    1    |
  • 컴파일러는 비트 연산(&, |, <<)을 자동으로 생성하여 위 구조체의 멤버에 접근합니다. 프로그래머는 비트 마스크를 직접 계산할 필요가 없어집니다.

3. The "Why" & Real-world Analogy (이유와 비유)

Q: 비트 필드는 편한데 왜 위험하다고 하나요?
A: "컴파일러 마음대로 데이터를 배치하기 때문입니다."

📦 이사짐 박스 포장 비유

  • 표준 타입(uint32_t): 규격화된 박스입니다. 누가 포장하든 똑같습니다.
  • 비트 필드: "이 물건들은 작으니까 빈틈에 대충 끼워 넣어"라고 지시하는 것과 같습니다.
    • A 이사업체(GCC): 왼쪽 구석부터 채움.
    • B 이사업체(MSVC): 오른쪽 구석부터 채움.
    • C 이사업체(ARM Compiler): "꽉 찼네? 그냥 다음 박스에 넣을게." (Padding 추가)

그래서 서로 다른 컴파일러를 쓰는 시스템 간에 통신할 때 비트 필드를 쓰면 데이터가 깨질 확률이 100%입니다. 또한, 앞서 배운 Endianness 문제와 결합되면 비트 순서가 뒤집혀서 대재앙이 일어납니다.


4. Code & Best Practice

비트(하드웨어)를 다루는 두 가지 방법

Method 1: 구조체 비트 필드 (가독성 위주, 같은 컴파일러 사용 시)

#include <stdint.h>

typedef union {
    uint8_t all; // 전체 8비트 접근용
    struct {
        uint8_t power  : 1;
        uint8_t bright : 3;
        uint8_t mode   : 4;
    } bits;
} LED_Control;

int main() {
    LED_Control led;
    led.all = 0; // 초기화
    
    // 직관적인 제어 (마치 변수처럼)
    led.bits.power = 1;  // 전원 켜기
    led.bits.bright = 5; // 밝기 5
    
    // 실제 하드웨어에 쓸 값 확인
    // 0000 0000 -> ??? (컴파일러/엔디언에 따라 다름!)
    return 0;
}

Method 2: 비트 연산자 (안전성 위주, 리눅스 커널 표준)

조금 귀찮더라도, 매크로와 비트 연산자(&, |, ~, <<)를 쓰는 것이 이식성에서 안전합니다. 어느 컴파일러든 똑같이 동작합니다.

#include <stdint.h>

// 레지스터 마스크 정의
#define MASK_POWER  (0x01)      // 0000 0001
#define MASK_BRIGHT (0x0E)      // 0000 1110
#define MASK_MODE   (0xF0)      // 1111 0000

// Shift 값 정의
#define SHIFT_BRIGHT (1)

int main() {
    uint8_t reg = 0;

    // 1. Power ON (Setting Bit)
    reg |= MASK_POWER;  
    
    // 2. Brightness 설정 (3단계 과정: Clear -> Shift -> Set)
    // 2-1. 기존 밝기 값 지우기 (Clear with AND NOT)
    reg &= ~MASK_BRIGHT; 
    // 2-2. 새로운 값(5)을 위치에 맞게 밀어넣기
    reg |= (5 << SHIFT_BRIGHT) & MASK_BRIGHT;

    return 0;
}

0개의 댓글