📢 여러 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을 보장해요
noinline은 inline으로 선언하지 않음을 명시하여 디버깅 중 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;
사용하지 않는 정적 변수나 파라미터에 붙여 불필요한 경고를 제거해요 (정적 분석기/빌드 클린 유지)
조건부 코드, 디버깅, 링커 참조, 정적 분석기 대응 등에 사용하면 돼요
더 많은 명령어들이 있지만 자주 사용하는 명령어를 알아봤어요
이 내용을 숙지하고 다시 한 번 코드를 보면 좀 더 깊은 의미를 깨달을 수 있어요!
'TIL > 2025' 카테고리의 다른 글
| 멀티코어 환경 section offset 기반 복사 & 재매핑 패턴 (0) | 2025.04.24 |
|---|---|
| 링커 스크립트 분석 5단계 (0) | 2025.04.24 |
| Compiler Extension & Builtin Function (0) | 2025.04.22 |
| 티스토리 기본 에디터에 inline code 추가 하기 (0) | 2025.04.21 |
| Tistory 코드 블럭 highlighting & 복사 버튼 추가하기 (0) | 2025.04.21 |