본문 바로가기
임베디드 기초

컴파일(Compile)이란 무엇인가?

by 버그없는토마토 2025. 12. 14.

C 코드가 실제 MCU에서 실행되는 과정 이해하기

Compile

안녕하세요, 버그없는토마토입니다 🍅
오늘은 임베디드 개발자라면 반드시 이해해야 하는 개념,
바로 컴파일(Compile)에 대해 정리해보겠습니다.

 

PC 프로그램이든 자동차 ECU든, 우리가 작성한 C 코드가 실제 하드웨어에서 어떻게 실행되는지 이해하지 못하면
디버깅·최적화·빌드 시스템을 제대로 사용할 수 없습니다.
특히 자동차 소프트웨어처럼 안전성이 중요한 분야에서는 “컴파일 과정 이해”는 필수 역량입니다.

 

컴파일의 개념을 이해하려면 사람의 언어와 마찬가지인 기계어라는 것을 이해해야 합니다.
기계의 입장이 되어서 바라보고 생각하면서 글을 읽어봐주세요.
그럼 이해하기 한결 편할거예요.

 

컴파일이란 무엇인가


1. 컴파일이란 무엇인가?

간단히 말하면,

사람이 이해할 수 있는 C 코드 → 기계(MCU)가 이해할 수 있는 기계어(0과 1)의 변환 과정

입니다.

 

MCU는 C 문법(쉽게말해 코드)을 이해하지 못합니다.
MCU가 이해하는 것은 오직 기계어(Instruction)뿐입니다.

따라서 우리가 작성한 소스코드
CPU 아키텍처(예: ARM Cortex-M, PowerPC, RISC-V)에 맞는 명령어로 바꿔주는 과정이 필요합니다.

이 과정을 담당하는 도구가 컴파일러(Compiler)입니다.

인간이 쓴 코드(C언어)를 기계가 이해할 수 있도록 기계어로 번역해주는 번역의 과정을 바로 컴파일이라고 표현합니다.
번역가컴파일러라고 부를 수 있죠.

 


2. 컴파일 과정은 한 번에 이루어지지 않는다

많은 분들이 “컴파일 = 번역”이라고 생각하지만, 실제로는 여러 단계로 이루어집니다.
컴파일러는 다음과 같은 절차를 거쳐 코드를 변환합니다.

 

인간의 코드를 바로 기계어로 변환하지 못합니다
여러 C파일과 H파일이 있는데 이들은 병렬적으로 구성되어있습니다
여러 컴포넌트가 분산되어있고, 양또한 방대하죠
이들은 각자 규칙을 가지고 우선순위를 가지고 있는데
이들을 모아서 규칙과 우선순위를 따져서 순서를 따져주는 과정을 거쳐야 합니다.

✔ ① 전처리 (Preprocessing)

#include, #define 등을 처리하는 단계입니다.

예:

#define VALUE 10

→ VALUE를 전부 10으로 치환

전처리 순서나 과정에 대해서 다음에 한번더 포스팅하도록 하겠습니다
이걸 이해하면 헤더파일을 통해 전처리 과정의 시간을 줄이고 메모리를 줄일 수 있는 꿀팁이 많기 때문이죠.

 


✔ ② 컴파일(Compile)

C 코드를 Assembly(어셈블리)로 변환하는 단계입니다.

예:

a = a + 1;


어셈블리:

 
LDR R0, [a]
ADD R0, #1
STR R0, [a]

 

생소한 개념이죠 어셈블리
기계어로 변환하기 전 언어라고 생각하시면 이해가 조금은 될 거예요

 


✔ ③ 어셈블(Assemble)

어셈블리 코드를 기계어(바이너리)로 변환합니다.

결국 기계어는 바이너리 입니다.
0101010로만 알아듣는거죠.

✔ ④ 링크(Link)

프로젝트 내 여러 파일(.o), 라이브러리, Startup 코드 등을 한데 묶어
최종 실행 파일(.elf, .bin)을 만듭니다.

링크 단계에서 오류 많이 발생함
(Undefined reference, Multiple definition 등)

링크에 대한 개념도 정말 중요합니다
개발을 하다보면 정말 많이 나오기도 하지만 나중에 문제가 났을때 링크는 문제를 찾아가는 중요한 역할을 하기도 하죠.

3. 임베디드 개발에서 컴파일이 중요한 이유

자동차 ECU 개발에서는 PC 개발과 달리 반드시 MCU 아키텍처에 맞는 컴파일러를 사용해야 합니다.

예:

  • ARM Cortex-M → ARM-GCC, IAR, KEIL
  • PowerPC 기반 ECU → Diab, WindRiver
  • S32K → GCC 기반 컴파일러 + Linker script

이유는 간단합니다.

MCU마다 명령어 체계가 다르기 때문

즉, 같은 C 코드라도
NXP S32K에서 동작하는 바이너리와
Infineon TC3xx에서 동작하는 바이너리는 완전히 다릅니다.

모두 다른 나라 사람들이라고 생각하시죠.
영어를 쓴다거나 일본어를 쓴다거나 중국어를 쓴다거나..
다 각자 맞는 통역가를 써야 대화를 할 수 있겠죠?

4. 링크(Link) 단계가 왜 중요한가?

임베디드 시스템의 Link 단계는 PC보다 훨씬 복잡합니다.
그 이유는 메모리 구조가 MCU마다 다르기 때문입니다.

  • Flash 시작 주소
  • RAM 시작 주소
  • 스택 크기
  • 벡터 테이블 위치
  • Bootloader 영역 분리

이 모든 것을 **링커 스크립트(.ld)**가 정의합니다.

즉,

링크가 잘못되면 프로그램이 '다운받기는 되는데 안 돌아가는' 문제가 발생한다.

임베디드에서는 컴파일 자체보다
링커 스크립트가 더 중요할 때도 많습니다.


5. Release vs Debug 빌드 차이 (실무 중요 포인트)

컴파일 옵션에 따라 코드가 완전히 달라집니다.

✔ Debug 빌드

  • 최적화 거의 없음
  • 디버깅 정보 포함
  • 크기가 큼
  • 실행 속도 느림

✔ Release 빌드

  • 최적화 강함(O3)
  • 디버깅 정보 없음
  • 크기 작음
  • 실행 속도 빠름
  • 최적화로 인해 디버깅이 어려움

자동차 ECU는 Release 빌드 최적화를 강하게 적용하는 경우가 많아
실제 컴파일 결과가 C 코드와 다르게 보이는 경우가 종종 발생합니다.

실무에선 주로 Debug빌드만을 사용합니다.

6. 컴파일 오류 vs 링크 오류

둘의 차이를 이해하는 것도 중요합니다.

❌ 컴파일 오류

문법이 잘못된 경우
예: 세미콜론 누락, 타입 안 맞음

❌ 링크 오류

참조된 함수·변수가 정의되지 않음
예:

undefined reference to Func_Init
실무에서 링크 오류가 훨씬 까다롭습니다.
컴파일 오류는 원인이 명확이지만 링크오류는 어딘가에서 Symbol이 빠졌다거나 전처리를 잘못할 경우 등 여러 방면에서 발생하므로 해결하기가 까다로운 편입니다.

🏁 마무리

컴파일은 단순히 “코드를 번역하는 과정”이라고만 생각한다면 25% 정답입니다.


C 코드 → 어셈블리 → 기계어 → 최종 실행파일로 이어지는 일련의 기술적 절차입니다.

임베디드 개발자는 컴파일러 옵션, 링크 스크립트, 아키텍처 특성을 반드시 이해해야 합니다.
특히 자동차 소프트웨어처럼 안전성이 중요한 분야에서는
빌드 과정이 전체 시스템 신뢰성에 직접적으로 영향을 미칩니다.

 

빌드와 컴파일, 링크와 디버그 이 개념이 확실히 들어있어야 어디서도 당당할 수 있습니다.

애매하게 알고 있는 사람 정말 많습니다

"설명해 봐"라고 얘기하면 거의 대부분은 추상적으로 얘기할 뿐입니다.

여러분은 정확히 짚어주세요.

 

오늘도 버그없는토마토였습니다 🍅