본문 바로가기
임베디드 개발/펌웨어

커스텀 UART 출력 함수도 printf처럼 컴파일러 경고 받기

by eteo 2026. 7. 8.
반응형

 

 

펌웨어 개발을 하다 보면 디버깅이나 로그 출력을 위해 가변 인자를 받는 커스텀 출력 함수를 직접 만들어 쓰곤 한다. 여기에 치명적일 수 있는 문제가 하나 있다..

 

먼저 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