C++의 설계 철학. "쓰지 않는 것에는 비용을 내지 않고, 쓰는 것은 직접 구현한 것보다 느리지 않다" (Bjarne Stroustrup).
What you don't use, you don't pay for. What you do use, you couldn't hand-code any better.
무엇이 무료인가 — 임베디드에서 안심하고 쓸 수 있는 것
| 기능 | 비용 |
|---|---|
| 클래스·멤버 함수 | 없음. this를 첫 인자로 넘기는 함수와 동일 |
| 네임스페이스 | 없음. 컴파일 시점 개념 |
| 템플릿 | 없음(런타임). 컴파일 시 코드 생성 |
constexpr | 없음. 컴파일 시 계산 |
| 참조 | 없음. 포인터와 같은 코드 |
| 인라인·operator 오버로딩 | 없음 |
static_cast | 없음. 컴파일 시 검사 |
| 강한 타입 열거(enum class) | 없음 |
| RAII | 없음. 소멸자 호출이 그대로 코드가 됨 |
클래스를 쓴다고 느려지지 않는다.
class Led { uint32_t pin; public: void on() { GPIOA->BSRR = pin; } };
led.on();
// → 컴파일 결과는 C의 led_on(&led) 와 동일하다
무엇이 비용을 갖는가
| 기능 | 비용 | 임베디드에서 |
|---|---|---|
| 가상 함수 | vptr + 간접 호출 | 필요한 곳에만 |
| 예외 | 테이블 수 KB~수십 KB, 스택 되감기 | 보통 끈다 |
| RTTI | 타입 정보 테이블 | 보통 끈다 |
| 동적 할당 | 단편화, 비결정적 시간 | 피한다 |
| iostream | 매우 크다(수십 KB) | 쓰지 않는다 |
std::function | 힙 할당 가능 | 주의 |
"쓰지 않으면 비용이 없다" 는 원칙 덕분에, 이것들을 안 쓰면 그만큼의 비용이 사라진다.
-fno-exceptions -fno-rtti -fno-threadsafe-statics
예외를 끄는 이유
- ① 코드 크기 — 스택 되감기 테이블이 크다
- ② 비결정적 — throw 부터 catch 까지 시간을 예측할 수 없다
- ③ 메모리 — 예외 객체 할당에 힙을 쓸 수 있다
- ④ 대안 부재 — 예외를 못 잡으면 std::terminate → 시스템 정지
대신 오류 코드나 std::expected(C++23) 스타일을 쓴다.
// -fno-exceptions 이면 표준 라이브러리의 throw 도 abort 가 된다
// std::vector::at() 같은 것을 쓰면 안 된다
템플릿 — 강력하지만 코드 팽창 주의
template <typename T> T max(T a, T b) { return a > b ? a : b; }
max(1, 2); // int 버전 생성
max(1.0, 2.0); // double 버전 생성
max<uint8_t>(…); // 또 하나
타입마다 별도 코드가 생성된다. 남용하면 플래시가 부풀어 오른다.
// 공통 부분을 비템플릿 함수로 빼는 기법
template <typename T> void process(T *p, size_t n) {
process_impl(static_cast<void*>(p), n, sizeof(T)); // 실체는 하나
}
constexpr — 런타임 비용을 컴파일 시점으로
constexpr uint32_t baud_to_brr(uint32_t clk, uint32_t baud) {
return (clk + baud / 2) / baud;
}
constexpr uint32_t BRR = baud_to_brr(84000000, 115200); // 컴파일 시 계산
USART1->BRR = BRR; // 상수 대입 명령 하나
계산이 플래시의 상수로 바뀐다. 매크로보다 안전하면서 비용은 같다. 임베디드 C++에서 가장 활용 가치가 높은 기능 중 하나다.
RAII — 임베디드에서 유용한 패턴
class CriticalSection {
uint32_t primask;
public:
CriticalSection() { primask = __get_PRIMASK(); __disable_irq(); }
~CriticalSection() { __set_PRIMASK(primask); }
};
void f() {
CriticalSection cs; // 진입
// …
if (error) return; // ★ 어느 경로로 나가도 자동 복원
} // 소멸자가 인터럽트를 복원
★ 해제를 빠뜨릴 수 없다. C에서 goto cleanup으로 하던 것을
언어가 보장해 준다. 비용은 0이고 안전성만 얻는다.
실무 지침
✅ 클래스, 네임스페이스, constexpr, enum class, RAII, 템플릿(절제)
- ⚠️ — 가상 함수 — 필요한 추상화에만 ❌ 예외, RTTI, iostream, 동적 할당, std::function(힙 할당 시)
"C++을 쓰되 부분집합을 쓴다" 가 임베디드의 현실적 답이다. MISRA C++와 AUTOSAR C++14 가이드라인이 이 부분집합을 규정한다.