안녕하세요, 후배 개발자 여러분! 지난 강의에서 우리는 하드웨어 독립적인 코드의 중요성을 다뤘습니다. 하지만 막상 코드를 짜다 보면 "결국 레지스터 직접 건드려야 하는데 어떻게 나누라는 거야?"라는 의문이 들죠. 솔직히 저도 연차 낮을 때는 소스 파일 하나에 전역 변수 수십 개 만들어서 개발하곤 했습니다. 😂 하지만 프로젝트 규모가 커지면 그건 지옥의 시작이에요. 오늘은 그 지옥에서 탈출시켜 줄 'C언어의 마법', 구조체와 함수 포인터를 활용한 모듈화 설계를 배워봅시다.
1. extern과 전역 변수가 우리 코드를 망치는 이유 🛑
프로젝트 여기저기서 extern int g_sensor_value; 같은 선언을 남발하고 계신가요? 이게 당장은 편하겠지만, 어느 순간 어디서 데이터가 바뀌었는지 찾을 수 없는 '데이터 오염' 상태에 빠지게 됩니다. 펌웨어 아키텍처 관점에서 전역 변수는 '공공재'와 같아서, 누구나 접근할 수 있다는 건 아무도 책임지지 않는다는 뜻이기도 하죠.
데이터는 반드시 그 데이터를 관리하는 모듈 내에 숨겨야 합니다. 이를 '캡슐화'라고 하죠. C언어에서는
static 키워드를 사용해 변수의 범위를 파일 내부로 제한하는 습관을 들여야 합니다.
2. 구조체: 데이터를 의미 있는 단위로 묶기 📦
단순히 변수만 묶는다고 설계가 끝나는 게 아닙니다. 구조체는 객체지향 언어의 '클래스'와 같은 역할을 수행할 수 있습니다. 예를 들어, LED를 제어한다면 포트 번호, 핀 번호, 현재 상태를 하나의 구조체로 묶어 관리하는 것이죠.
| 설계 방식 | 특징 및 장점 |
|---|---|
| 변수 나열 방식 | 코드 가독성이 떨어지고, 멀티 인스턴스(여러 개의 LED) 대응이 힘듦 |
| 구조체 캡슐화 | 데이터가 그룹화되어 관리가 쉽고, 동일한 구조체를 재사용하여 확장 용이 |
3. 함수 포인터로 구현하는 '인터페이스' 🔌
이게 오늘 강의의 핵심입니다. 하위 레이어(하드웨어 드라이버)가 상위 레이어(애플리케이션 logic)의 함수를 호출해야 할 때, 직접 함수를 호출하면 강한 결합이 생깁니다. 이때 함수 포인터를 구조체 멤버로 넣으면, 마치 C++의 추상 클래스처럼 동작하게 만들 수 있습니다.
드라이버 구조체 예시 📝
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
void (*on)(struct LedDriver* self); // 함수 포인터
void (*off)(struct LedDriver* self);
} LedDriver;
위와 같이 설계하면, 상위 레이어는 실제 하드웨어가 STM32인지 AVR인지 몰라도 driver->on()만 호출하면 됩니다. 이게 바로 하드웨어 추상화의 시작이죠!
C언어 모듈화 핵심 요약
자주 묻는 질문 ❓
오늘 내용이 조금 어려웠다면 코드를 직접 타이핑해 보시길 권장합니다. 백문이 불여일타니까요! 함께 성장합시다! 😊

