HW부터 네이티브 코드까지 — C/C++/Rust/Go 실행 구조

한 문장 요약

소스 코드 → 컴파일러 → 기계어 바이너리 → OS 로더 → CPU가 트랜지스터 수준에서 직접 실행


전체 실행 스택 (위→아래: 추상화 높음→낮음)

┌─────────────────────────────────────────────┐
│            소스 코드 (C / C++ / Rust / Go)    │
│  int main() { printf("hello\n"); return 0; } │
└────────────────────┬────────────────────────┘
                     │  컴파일러
                     │  (gcc / clang / rustc / go build)
                     ▼
┌─────────────────────────────────────────────┐
│              전처리 (Preprocessor)           │
│  #include, #define 매크로 치환               │
│  → .c → 확장된 .c                           │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│              컴파일 (Compiler)               │
│  C 문법 파싱 → AST → IR → 최적화            │
│  → .s (어셈블리 코드)                        │
│                                             │
│  예시 어셈블리:                              │
│    mov eax, 1                               │
│    add eax, ebx                             │
│    ret                                      │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│              어셈블 (Assembler)              │
│  어셈블리 → 기계어 오브젝트 파일            │
│  → .o / .obj                                │
│  (각 소스 파일마다 1개의 .o 생성)            │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│              링킹 (Linker)                   │
│  여러 .o + 라이브러리(.a / .so / .dll) 결합  │
│  → 실행 가능한 바이너리 (ELF / Mach-O / PE) │
│                                             │
│  바이너리 내부 구조:                         │
│  ┌───────────┐                              │
│  │  .text    │  기계어 명령어               │
│  │  .data    │  초기화된 전역변수           │
│  │  .bss     │  초기화 안 된 전역변수       │
│  │  .rodata  │  상수 (문자열 리터럴 등)     │
│  └───────────┘                              │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│        OS 실행 요청 (./a.out 또는 클릭)      │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│            커널 (Kernel)                     │
│                                             │
│  1. execve() 시스템 콜 수신                  │
│  2. ELF 헤더 파싱 → 포맷 확인               │
│  3. 새 프로세스 생성 (PCB 할당)              │
│  4. 가상 주소 공간 설정                      │
│     ┌──────────────────────┐                │
│     │  Stack (↓ 성장)      │ 지역변수, 콜스택│
│     │  ...                 │                │
│     │  Heap  (↑ 성장)      │ malloc/new     │
│     │  .bss                │                │
│     │  .data               │                │
│     │  .text  (기계어)     │                │
│     └──────────────────────┘                │
│  5. 동적 링커(/lib/ld.so) 실행              │
│     - libc.so 등 공유 라이브러리 매핑        │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│            CPU 명령어 실행 사이클            │
│                                             │
│  PC(Program Counter) → 명령어 주소 가리킴   │
│                                             │
│  ┌─────────────────────────────────────┐   │
│  │  Fetch  → Decode  → Execute  → WB   │   │
│  │  (인출)   (해석)    (실행)    (저장)  │   │
│  └─────────────────────────────────────┘   │
│                                             │
│  레지스터: rax, rbx, rsp, rip ...           │
│  ALU: 덧셈/뺄셈/비교 연산                  │
│  Cache: L1(~ns) → L2 → L3 → RAM(~100ns)   │
└────────────────────┬────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│            트랜지스터 / 전기 신호            │
│                                             │
│  0 = 0V (LOW)   1 = ~3.3V / 5V (HIGH)      │
│  MOSFET 트랜지스터: 스위치 ON/OFF           │
│  수십억 개의 트랜지스터가 논리 게이트 구성   │
│  AND / OR / NOT → 반가산기 → 전가산기 →     │
│  ALU → 레지스터 → 파이프라인 → CPU         │
└─────────────────────────────────────────────┘

컴파일 언어별 비교

언어       컴파일러       중간 표현        최종 결과
─────────────────────────────────────────────────
C          gcc/clang      AST → LLVM IR   ELF/Mach-O
C++        g++/clang++    AST → LLVM IR   ELF/Mach-O
Rust       rustc          MIR → LLVM IR   ELF/Mach-O
Go         go build       SSA             ELF/Mach-O (정적 링크)

Go의 특이점: 정적 링크

일반 C 실행 파일:
  my_app → 실행 시 libc.so, libpthread.so 동적 로드 필요

Go 실행 파일:
  my_app → 런타임 + 표준 라이브러리 전부 포함 (self-contained)
           Go 스케줄러(goroutine M:N 스레드) 내장
           GC(가비지 컬렉터) 내장

Rust의 특이점: 소유권이 컴파일 타임에 처리됨

Rust 컴파일러 단계:
  소스 → 파싱 → HIR → MIR(소유권/수명 검사) → LLVM IR → 기계어

MIR 단계에서:
  - Borrow Checker: 메모리 안전성 검증
  - 런타임 GC 없음 → 컴파일 타임에 drop() 삽입
  - 결과: C/C++ 수준 성능 + 메모리 안전

메모리 레이아웃 (실행 중 프로세스)

가상 주소 공간 (64bit, 높은 주소 → 낮은 주소)

0xFFFF_FFFF_FFFF_FFFF ┌──────────────────┐
                      │  커널 공간        │ (접근 불가)
0xFFFF_8000_0000_0000 ├──────────────────┤
                      │  Stack           │ 함수 호출, 지역변수
                      │  ↓               │ (자동 할당/해제)
                      │                  │
                      │  ↑               │
                      │  Heap            │ malloc/new (수동 관리)
                      ├──────────────────┤
                      │  .bss            │ 미초기화 전역변수
                      │  .data           │ 초기화 전역변수
                      │  .rodata         │ 상수, 문자열
                      │  .text           │ 기계어 명령어
0x0000_0000_0040_0000 ├──────────────────┤
                      │  NULL (미사용)   │
0x0000_0000_0000_0000 └──────────────────┘

핵심 요약

소스코드(.c/.rs/.go)
    ↓ 컴파일러 (전처리 → 파싱 → IR → 최적화 → 어셈블리)
어셈블리(.s)
    ↓ 어셈블러
오브젝트(.o)
    ↓ 링커 (심볼 해결 + 라이브러리 결합)
바이너리 (ELF/Mach-O/PE)
    ↓ OS execve() → 가상 메모리 매핑
프로세스 (가상 주소 공간: text/data/heap/stack)
    ↓ CPU Fetch-Decode-Execute
트랜지스터 ON/OFF (전기 신호)