TIL/2025

주요 Compile Extension & Builtin Function 예시를 알아보자

고무 오리 2025. 4. 23. 23:46
728x90
📢 여러 Compiler Extension & Builtin Function 키워드 중 자주 사용하는 것들을 예제와 함께 익혀봐요

 

 

1) 최적화 힌트

 분기 예측 최적화

__builtin_expect

#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

void check_error(int status) {
    if (unlikely(status != 0)) {
        handle_error();
    }
}

 

 

분기문이 있으면 CPU는 아래와 같이 분기 예측(branch prediction)을 함

"어차피 분기할 건데, 조건 결과 기다리지 말고 미리 추측해서 다음 명령 실행해볼까?”

 

이 예측이 성공하면 성능이 매우 좋아지고

예측 실패하면 파이프라인을 flush(비우고 다시 실행)해야 해서 성능 손해가 크죠

(예측 실패 1회 = 수십 클럭 낭비)

 

그래서 __builtin_expect를 사용해서 예측 방향을 힌트로 알려 성능을 최적화 해요

특히 루프 내 분기에서 성능에 민감한 코드에서 효과적이죠

 

최적화 여부 명시

__restrict__ 을 사용해 사용되는 포인터 간 독립적임을 알려줘요

두 포인터가 서로 겹치지 않는다는 힌트를 컴파일러에 줘서 메모리 접근을 더 공격적으로 최적화 할 수 있

void copy(__restrict__ char* dest, __restrict__ const char* src) {
    while (*src) *dest++ = *src++;
}

 

__volatile__ 을 사용 해당 변수/asm 블록 최적화를 방지해요

메모리-mapped 레지스터나 I/O 접근 시 필수에요

volatile int* status = (volatile int*)0x40000000;
int value = *status;

 

 

2. 함수 성격 명시

__attribute__((noreturn))

__attribute__((interrupt("IRQ")))
void irq_handler(void) {
    clear_irq_flag();
}

 

컴파일러 혹은 정적 분석기가 "오류나 경고"로 오인할 수 있는 코드 흐름을 미리 알려줘서 불필요한 경고를 없애고, 분석 정확도를 높일 수 있어요

 

__attribute__((interrupt))

__attribute__((interrupt("IRQ")))
void irq_handler(void) {
    clear_irq_flag();
}

 

컴파일러는 해당 함수가 인터럽트에서 호출된다는 것을 인식하고, 레지스터와 스택 처리, 리턴 방식 등을 자동으로 조정해요

 

 

3. 메모리 위치 제어

__attribute__((section))__attribute__((used))

__attribute__((section(".boot_vector")))
__attribute__((used))
void (*const boot_entry)(void) = my_boot_function;

 

특정 함수나 데이터를 링커가 원하는 위치에 배치하게 하는 힌트에요

  • 부트로더, 인터럽트 벡터, 특정 주소 매핑 등에 필요하죠

used 와는 세트로 쓰여요

  • 함께 사용하지 않으면 “이거 선언만 해놓고 쓰는데가 없네?”하며 최적화로 제거될 수 있어요

 

 

4. 하드웨어 직접 접근

asm volatile

void disable_irq() {
    asm volatile("cpsid i");
}

 

inline assembly로 C/C++코드에서 assembly를 사용 가능해요

  • ARM 같은 아키텍처에서 레지스터나 인터럽트를 제어하죠

 

 

5. 구조체 정렬 제어

__attribute__((packed))__attribute__((aligned))

 

구조체 필드 오프셋 정렬은 성능 향상을 위한 거에요

  • 성능 향상을 위해 구조체 정의 시 보통 아래와 같이 padding을 채워 메모리 배치되죠
struct Normal {
    uint8_t  a;  // 1 byte
    uint16_t b;  // 2 byte
};

// 메모리 배치:
// [ a ][PAD][ b ][ b ]

 

그치만 HW 레지스터 값을 구조체에 담아 접근하려면 정렬로 인한 padding 때문에 이상 동작 해버리죠

그래서 packed를 붙여 컴파일러에게 padding을 사용하지 말라고 해주는 것이죠

  • 하드웨어 제어용 레지스터 구조체에 거의 필수
// 레지스터 매핑 (packed)
struct __attribute__((packed)) Reg {
    uint8_t ctrl;
    uint16_t status;
};
#define REG_ADDR 0x40000000
volatile Reg* reg = (Reg*)REG_ADDR;

 

aligned 는 정반대의 역할을 해요

  • 필드를 정렬 맞춰 배치(padding 삽입)하며 DMA, SIMD, 캐시 최적화에 사용돼요
// DMA 버퍼 (aligned)
uint8_t dma_buf[256] __attribute__((aligned(32)));

 

 

6. 기본 함수 제공 (약한 심볼)

__attribute__((weak))

ISR을 개발할 때 모든 핸들러를 한 번에 다 구현하기 어려우니, 아직 구현 안된 ISR들을 weak 속성으로 정의해 둬요

// 최초에 정의
__attribute__((weak)) void UART_IRQHandler(void) {
    while (1);  // default: 멈춰!
}

 

나중에 실제 함수를 구현하면 그 구현이 override 되어 ISR로 동작해요

// 나중에 구현
void UART_IRQHandler(void) {
    printf("UART interrupt handled!\n");
}

 

 

7. 컴파일 타임 조건 처리

__builtin_constant_p

#define SQUARE(x) \
    (__builtin_constant_p(x) ? ((x)*(x)) : square_runtime(x))

int square_runtime(int x) { return x * x; }

 

SQUARE(x)를 사용 할 때,

  • SQUARE(5) 이면, __builtin_constant_p(x) 가 true라 상수 25로 대체돼요
  • SQUARE(variable) 이면, __builtin_constant_p(x) 가 false라 square_runtime(variable) 로 대체돼요

 

 

8. 프로그램 중단

__builtin_trap()

void assert_failed() {
    __builtin_trap(); // 즉시 fault 발생
}

 

__builtin_trap()은 "이 코드에 도달하면 프로그램이 중단된다"는 것을 컴파일러나 분석 도구에게 명확히 알려주는 도구에요

“이 지점에 절대 도달하지 않아야 한다”는 의도를 명시적으로 전달”하니 예외 처리 시스템, static analyzer와 연동 시 매우 유용하죠

 

 

9. 인라인 여부 강제

__attribute__((always_inline))__attribute__((noinline))

__attribute__((always_inline)) inline int fast_add(int a, int b) {
    return a + b;
}

 

inline선언을 하면 뭐가 좋은점이 뭘까요?

  • 함수 호출 대신 코드 자체를 삽입해 함수 호출 오버헤드를 없애고, 코드를 빠르게 실행 가능해요.

inline 키워드만 사용하면 컴파일러에 의해 거의 인라인 되지만, 보장되지는 않아요

always_inline은 빠른 성능이 중요한 함수(ISR 경로, 핫 루프 내 계산)에 사용해서 inline을 보장해요

noinlineinline으로 선언하지 않음을 명시하여 디버깅 중 Function Call Trace를 남기고 싶을 때 사용해요

 

특히 ISR이나 핫 루프는 “함수 호출 오버헤드조차 허용되지 않는” 빠르게 수행되어야 하는 환경이기 때문에, 연산이 작고 반복되는 함수는 always_inline이 성능과 응답 시간을 보장하는 길이에요

 

 

10. C 인터페이스 연결

extern "C"

#ifdef __cplusplus
extern "C" {
#endif

void legacy_c_api();

#ifdef __cplusplus
}
#endif

extern "C" void start();

 

extern "C" 는 C++의 이름 맹글링(name mangling)을 막고, 함수 이름을 C 스타일 그대로 유지하게 해줘요

따라서 어셈블리나 C 코드, 링커에서 C++ 함수를 호출하려면 반드시 extern "C" 로 선언해줘야 해요

 

 

11. 경고 제거 및 정적 분석 대응

__attribute__((unused))

__attribute__((unused)) static int debug_only = 42;

 

사용하지 않는 정적 변수나 파라미터에 붙여 불필요한 경고를 제거해요 (정적 분석기/빌드 클린 유지)

조건부 코드, 디버깅, 링커 참조, 정적 분석기 대응 등에 사용하면 돼요

 

 

 

더 많은 명령어들이 있지만 자주 사용하는 명령어를 알아봤어요

이 내용을 숙지하고 다시 한 번 코드를 보면 좀 더 깊은 의미를 깨달을 수 있어요!

728x90