임베디드 C 필수 지식: 마이크로컨트롤러 프로그래밍 핵심 Q&A
마이크로컨트롤러 프로그래머는 제한된 자원 환경에서 C 언어의 특성을 완벽히 익혀야 합니다. 아래는 컴파일, 메모리 관리, 최적화에 초점을 맞춘 실무 중심 질문 목록입니다. 이러한 주제는 임베디드 시스템 기업의 인터뷰에서 자주 등장합니다.
주요 키워드와 사용법
Static은 변수의 가시성을 현재 파일이나 함수로 제한하며, 로컬 정적 변수의 경우 호출 간 값이 유지됩니다. Volatile은 외부(레지스터, 메모리, 인터럽트)에서 변경되는 변수에 대해 컴파일러 최적화를 막습니다.
문제 있는 코드 예시:
int square(volatile int *ptr) {
return *ptr * *ptr;
}
여기서 volatile은 첫 번째 역참조에만 적용되며, 두 번째는 최적화로 사라질 수 있습니다. 수정: (ptr) (*ptr).
Const volatile은 상태 레지스터처럼 읽기 전용 외부 데이터에 적합합니다. Register는 CPU 레지스터 저장을 제안하지만(C11에서 deprecated). Restrict는 포인터 별칭 없음을 알려 벡터화 최적화를 돕습니다.
메모리 관리와 구조체
전역 const 변수는 플래시 메모리에 저장됩니다. 32KB 크기의 0xFF로 채운 상수 배열은 링커 스크립트에서 fill(0xFF) 섹션을 사용하세요.
비트 연산: 비트 확인 n & (1 << k), 클리어 n & ~(1 << k). 부동소수점 비교: fabs(a - b) < EPSILON.
구조체 크기는 정렬에 따라 달라집니다. 다음의 경우:
struct Foo {
int iiii;
char c;
};
크기는 8바이트(4바이트 패딩). GCC 패킹: __attribute__((packed)).
문자열 선언 예시:
char str5[]={'s','t','r','i','n','g'}; // 크기:7 길이:6
char *str1="string"; // 크기:4 길이:6 (포인터)
char str3[]="string"; // 크기:7 길이:6
char str4[10]="string"; // 크기:10 길이:6
빌드 시스템과 툴체인
빌드 시스템은 컴파일, 링킹, 의존성을 자동화합니다. Make는 파일 변경을 처리하나, Ninja는 병렬 처리와 간결함으로 더 빠릅니다.
VPATH는 GNU Make에서 지정 디렉터리에서 소스 검색.
코드 파이프라인: .c → .i (전처리기) → .s (어셈블리) → .o → .elf → .bin/.hex.
GCC 경고 비활성화: #pragma GCC diagnostic ignored "-Wrestrict".
펌웨어 축소: -ffunction-sections -fdata-sections, ld --gc-sections, -Os 최적화.
빌드 아티팩트
.o— 오브젝트 파일.elf— 심볼 포함 실행 파일.hex/.bin— 플래싱용 (hex는 주소 포함)
Binutils: objdump(디스어셈블러), objcopy(변환기), readelf(ELF 분석기).
빌드 확인: make V=1 또는 -n (드라이 런).
자료구조와 알고리즘
원형 버퍼 — 헤드/테일 인덱스를 가진 링 배열(FIFO는 요소 이동).
연결 리스트 제거: prev->next = node->next (prev 필요) 또는 node->next->prev = node->prev (양방향).
C에서의 다형성: 구조체 내 함수 포인터(vtable 스타일).
if/switch 없는 작업:
void print_num(int n) {
const char *nums[] = {"","One","Two"};
printf("%s", nums[n]);
}
do-while(0) 매크로 — 여러 문장 단일 반복.
인터럽트와 RTOS
인터럽트 — 이벤트 발생 시 ISR로 점프하는 하드웨어 메커니즘. 벡터 테이블에 ISR 주소 저장. Cortex-M4: 진입 시 12–16 사이클(컨텍스트 저장).
흐름: 하드웨어 PC/LR 저장, 벡터 로드, ISR 점프, POP으로 반환.
Reentrant 함수 — 동시 호출 안전(정적/로컬 동기화 필요).
RTOS에서:
- Thread — 독립 스택 실행 단위
- Mutex — 상호 배제
- Semaphore — 이벤트 카운터
- Spinlock — 바쁜 대기 락
코드 문제: 락 순서 데드락(TaskA: ma→mb, TaskB: mb→mc→ma).
Priority inversion: 고우선순위 태스크가 저우선순위 대기. 해결: 우선순위 상속.
원자 연산
예시: __atomic_exchange_n(&x, &y, __ATOMIC_SEQ_CST).
비트 밴딩 — 마스크 없이 직접 비트 접근(Cortex-M3/4).
툴체인과 디버깅
ABI — 바이너리 인터페이스(호출 규약).
세그먼트: .text(플래시 코드), .data(RAM 초기화), .bss(RAM 0 초기화), .rodata(플래시 상수).
전처리기 확장: gcc -E file.c > expanded.i.
주요 문서: 데이터시트, 레퍼런스 매뉴얼, 프로그래밍 매뉴얼, 회로도.
핵심 요약
- 주변기기 레지스터에 volatile 필수
- 패킹 구조체는 RAM 절약하나 접근 느림
- RTOS는 스레드 안전 코드와 크리티컬 섹션 보호 필요
- 빌드 최적화: 섹션 + gc-sections
- 인터럽트는 현재 스레드 스택 사용
구조체 0xFF 패딩: 링커 스크립트 fill 또는 __attribute__((packed)) + 수동 채움.
— Editorial Team
아직 댓글이 없습니다.