C와 어셈블리 대조: 최적화 옵션이 바꾸는 것
간단한 C 함수를 디스어셈블해서 한 줄씩 맞춰 보고, -O0과 -O2가 같은 코드를 얼마나 다르게 만드는지 비교합니다.
디스어셈블 보는 방법
arm-none-eabi-objdump -d -S firmware.elf > fw.lst
-S를 붙이면 C 소스와 명령어가 섞여 나옵니다. STM32CubeIDE에서는 디버그 중 Disassembly 창을 열면 됩니다.
같은 함수, 다른 결과
int sum(const int *a, int n) {
int s = 0;
for (int i = 0; i < n; i++) s += a[i];
return s;
}
- -O0: 변수
s,i를 매번 스택에 저장하고 다시 읽습니다. 디버깅하기 좋지만 느립니다. - -O2:
s,i를 레지스터에만 두고, 포인터를 직접 증가시키며 루프를 돕니다. 명령어 수가 절반 이하로 줄어듭니다.
최적화가 드러내는 버그
-O2에서만 동작이 이상해진다면 대개 다음 중 하나입니다.
- 인터럽트와 공유하는 변수에
volatile이 없음 → 루프 밖으로 읽기가 빠짐 - 정의되지 않은 동작(부호 있는 오버플로, 초기화 안 된 변수)
- 지연 루프
for (i = 0; i < 1000; i++);가 통째로 사라짐
"최적화하면 망가진다"가 아니라 "최적화가 원래 있던 버그를 드러냈다"로 봐야 합니다.