펌웨어 개발을 하다 보면 디버깅이나 로그 출력을 위해 가변 인자를 받는 커스텀 출력 함수를 직접 만들어 쓰곤 한다. 여기에 치명적일 수 있는 문제가 하나 있다..
먼저 C 표준인 printf 함수를 쓰면 컴파일러가 전달된 포맷 문자열(Format string)과 입력된 인자(Argument)의 개수 및 자료형이 일치하는지를 확인하고, 그렇지 않으면 경고를 표시해서 개발자의 실수를 예방해준다.
반면 내가 직접 만든 함수는 컴파일러가 해당 검사를 수행하지 않기 때문에, 전달한 변수 타입과 % 서식지정자가 맞지 않거나 인자 개수가 안 맞는 경우, 엉뚱한 값이 출력될 수 있고 최악의 경우 잘못된 메모리에 접근해서 Hardfault가 발생할 수 있다.
문제 상황 예시.
uint32_t internal_temp = 45;
UART_Printf(&huart1, "Temp: %hu\n", internal_temp); // 32비트 변수에 %hu(16비트) 사용
UART_Printf(&huart1, "%d %d\n", 10); // 인자 개수 불일치
어떻게 해결하면 될까?
내가 만든 커스텀 함수도 표준 printf처럼 컴파일 타임에 타입 검사를 하라고 컴파일러에게 알려주면 된다.
이를 위해 GCC 확장 문법인 __attribute__ 를 쓰면되는데, 모든 컴파일러가 이 문법을 지원하는 게 아니므로 __GNUC__ 매크로를 사용해 GCC 계열 혹은 GCC 확장 문법을 지원하는 컴파일러인지 확인하는 코드를 포함해 정의하는 것이 좋다. 참고로 Clang 컴파일러도 GCC 확장 문법을 거의 완벽히 지원하므로 __GNUC__ 가 정의된다.
#if defined(__GNUC__)
#define ATTR_FORMAT_PRINTF(fmt, args) __attribute__((format(printf, fmt, args)))
#else
#define ATTR_FORMAT_PRINTF(fmt, args)
#endif
// void UART_Printf(UART_HandleTypeDef *huart, const char *fmt, ...);
void UART_Printf(UART_HandleTypeDef *huart, const char *fmt, ...) ATTR_FORMAT_PRINTF(2, 3);
- __attribute__((format(printf, fmt, args))) 의 의미
- __attribute__: GCC 확장문법. 함수에 쓸 때는 함수 선언문 뒤에 붙이고 세미콜론은 전체 문장 끝에 한번만 붙인다.
- printf : printf 계열의 포맷 문법과 타입 검사 규칙을 적용한다.
- fmt : 함수 인자 중 몇 번째가 서식 지정자 문자열인지 알려준다.
- args : 함수 인자 중 몇 번째 부터 가변인자가 시작되는지 알려준다.
이렇게 해두고 컴파일 옵션에 -Wformat (또는 전체 경고를 켜는 -Wall)을 활성화하면, 컴파일 타임에 printf처럼 인자의 유형과 개수가 지정된 형식 지정자를 준수하는지 확인하고, 그렇지 않으면 경고를 띄운다.
추가 Tip.
컴파일러 환경에 따라 같은 타입인데도 서식 지정자 워닝이 뜰 수 있다. 예를 들어 uint32_t는 어느 환경에서나 32비트 정수지만, 어떤 컴파일러는 이걸 unsigned int로 정의해서 %u를 써야하고 어떤 컴파일러는 unsigned long으로 정의해서 %lu를 써야 할 수도 있다.
이런 경우 <inttypes.h>의 출력 매크로를 쓰면 알아서 적절한 서식 지정자로 치환되기 때문에 플랫폼 독립적인 코드를 짤 수 있다.
#include <inttypes.h>
uint32_t count = 123456;
// %"PRIu32" 를 사용하면 플랫폼에 상관없이 32비트 unsigned 정수에 맞는 서식 지정자로 자동 치환된다.
printf("count: %" PRIu32 "\n", count);
'임베디드 개발 > 펌웨어' 카테고리의 다른 글
| 시정수(Time Constant, τ) (0) | 2026.07.15 |
|---|---|
| NCP303L 데이터시트 분석 (0) | 2026.07.11 |
| 1553B (MIL-STD-1553) (0) | 2026.06.07 |
| TL16C752C, Dual UART (16550 호환 칩) (0) | 2026.05.31 |
| UART 흐름제어 (RTS/CTS, DTR/DSR, XON/XOFF) (0) | 2026.05.24 |