"C언어의
int는 고무줄입니다. 하드웨어 제어에는 '강철 자'가 필요합니다."
int, long): CPU 아키텍처(16bit, 32bit, 64bit)에 따라 크기가 변합니다. 이식성(Portability)이 없습니다.uint32_t 등): 어떤 환경에서도 크기가 절대 변하지 않습니다. 하드웨어 레지스터와 통신 규약(Protocol)을 다룰 때 필수입니다.하드웨어 레지스터를 직접 제어하는 상황을 시각화해 보겠습니다.
int의 배신 (왜 stdint.h를 써야 하는가)여러분이 32비트 PC에서 long(4바이트)을 써서 코드를 짰는데, 64비트 서버로 가져가니 long이 8바이트가 되어버렸습니다. 메모리 레이아웃이 완전히 틀어집니다.
그래서 1999년(C99 표준), 구원투수가 등장했습니다: <stdint.h>
| 기존 타입 (불확실) | 고정 크기 타입 (확실) | 의미 |
|---|---|---|
unsigned char | uint8_t | 부호 없는 8비트 (1 Byte) |
unsigned short | uint16_t | 부호 없는 16비트 (2 Bytes) |
unsigned int | uint32_t | 부호 없는 32비트 (4 Bytes) |
unsigned long long | uint64_t | 부호 없는 64비트 (8 Bytes) |
"임베디드나 커널 코드에서는
int를 쓰지 마십시오. 루프 카운터(i) 정도에만 쓰세요. 데이터 구조체에는 무조건uint*_t를 쓰십시오."
하드웨어의 제어 레지스터(Control Register)는 보통 32비트 안에 온갖 스위치를 욱여넣습니다.
[상황] 가상의 LED 제어 레지스터 (8비트)
이걸 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 |
&, |, <<)을 자동으로 생성하여 위 구조체의 멤버에 접근합니다. 프로그래머는 비트 마스크를 직접 계산할 필요가 없어집니다.Q: 비트 필드는 편한데 왜 위험하다고 하나요?
A: "컴파일러 마음대로 데이터를 배치하기 때문입니다."
uint32_t): 규격화된 박스입니다. 누가 포장하든 똑같습니다.그래서 서로 다른 컴파일러를 쓰는 시스템 간에 통신할 때 비트 필드를 쓰면 데이터가 깨질 확률이 100%입니다. 또한, 앞서 배운 Endianness 문제와 결합되면 비트 순서가 뒤집혀서 대재앙이 일어납니다.
비트(하드웨어)를 다루는 두 가지 방법
#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;
}
조금 귀찮더라도, 매크로와 비트 연산자(&, |, ~, <<)를 쓰는 것이 이식성에서 안전합니다. 어느 컴파일러든 똑같이 동작합니다.
#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;
}