포스트

CS — runtime

컴파일 타임과 런타임의 차이, RTTI와 런타임 에러를 면접 답변 흐름으로 정리

CS — runtime

모의면접 답변 — “런타임이란 무엇인가요?”

런타임(Runtime)은 프로그램이 실행되고 있는 시간적 단계를 의미합니다. 컴파일 타임이 소스 코드를 기계어로 변환하는 단계라면, 런타임은 그 결과물이 실제로 OS 위에서 동작하는 단계입니다. 런타임에는 동적 메모리 할당과 해제, 가상 함수를 통한 동적 디스패치, RTTI를 활용한 타입 판별 등이 일어납니다. 런타임 에러는 컴파일은 통과하지만 실행 중에 발생하는 오류로, 세그멘테이션 폴트, 스택 오버플로우, 정의되지 않은 동작(UB) 등이 대표적입니다. 언리얼 엔진에서도 BeginPlay(), Tick() 같은 가상 함수는 런타임에 vtable을 통해 동적 디스패치되며, IsA()나 Cast()는 RTTI 기반으로 런타임 타입 검사를 수행합니다.

키워드 정리

기본 개념

  • 런타임 (Runtime) — 프로그램이 실행 중인 단계; 컴파일 타임 이후 OS 위에서 동작하는 시간
  • 컴파일 타임 (Compile Time) — 소스 코드를 기계어로 변환하는 단계; 타입 검사·최적화 발생
  • 동적 메모리 할당 (Dynamic Memory Allocation) — 런타임에 힙에서 메모리를 요청 (new / malloc)
  • RTTI (Runtime Type Information) — 런타임에 객체의 실제 타입을 조회하는 C++ 메커니즘
  • 동적 디스패치 (Dynamic Dispatch) — vtable을 통해 런타임에 실제 함수를 결정·호출
  • 정의되지 않은 동작 (Undefined Behavior, UB) — 표준이 결과를 규정하지 않는 코드; 런타임에 예측 불가

키워드 분류표

  • 타입 검사 → 컴파일 타임 (static_assert, 템플릿 인스턴스화)
  • 동적 디스패치 → 런타임 (virtual 함수 호출, vtable 참조)
  • RTTI → 런타임 (dynamic_cast, typeid)
  • 메모리 할당 → 런타임 (new/delete, malloc/free)
  • 런타임 에러 → 런타임 (segfault, 스택 오버플로우, UB)

컴파일 타임 vs 런타임

컴파일 타임은 소스 코드를 기계어로 번역하는 단계로, 문법 검사·타입 검사·최적화가 일어납니다. 런타임은 컴파일된 프로그램이 OS 위에서 실제로 실행되는 단계로, 메모리 할당·동적 디스패치·I/O 등이 발생합니다.

비교:

  • 시점: 빌드 시 / 실행 시
  • 주요 작업: 문법·타입 검사, 최적화, 코드 생성 / 메모리 할당, 동적 디스패치, I/O
  • 에러 발견: 컴파일 에러 (즉시 피드백) / 런타임 에러 (실행 중 크래시)
  • 성능 비용: 빌드 시간에 소모 / 실행 중 소모 (직접 영향)
1
2
3
4
5
6
7
8
9
10
11
12
// ── 컴파일 타임 ──────────────────────────────────────────
static_assert(sizeof(int) == 4, "int는 4바이트여야 함");
constexpr int Square(int x) { return x * x; }
template<typename T> void Print(T val) { }

// ── 런타임 ───────────────────────────────────────────────
int n;
std::cin >> n;                    // 런타임에 값 결정
int* arr = new int[n];            // 런타임 메모리 할당
Base* p = new Derived();          // 런타임 객체 생성
p->VirtualFunc();                 // 런타임 동적 디스패치
delete[] arr;                     // 런타임 메모리 해제

복기:

  • 컴파일 타임 비용 = 빌드 시간 증가 (실행 성능에 무관)
  • 런타임 비용 = 실제 실행 속도에 직접 영향
  • constexpr / 템플릿 / static_assert → 컴파일 타임으로 비용 이동
  • virtual / new / dynamic_cast → 런타임 비용 발생

RTTI (Runtime Type Information)

RTTI는 런타임에 객체의 실제(동적) 타입을 조회할 수 있게 해주는 C++ 메커니즘입니다. dynamic_casttypeid 두 가지 방법으로 사용하며, 가상 함수가 있는 클래스(vtable 보유)에서만 동작합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal { public: virtual ~Animal() {} };
class Dog    : public Animal { public: void Bark() {} };
class Cat    : public Animal {};

Animal* a = new Dog();

// 1. dynamic_cast — 안전한 다운캐스트
Dog* d = dynamic_cast<Dog*>(a);  // 성공
if (d) d->Bark();

Cat* c = dynamic_cast<Cat*>(a);  // 실패: nullptr 반환

// 2. typeid — 타입 정보 조회
#include <typeinfo>
std::cout << typeid(*a).name();
if (typeid(*a) == typeid(Dog)) { /* Dog임을 확인 */ }

dynamic_cast vs static_cast:

  • 검사 시점: 컴파일 타임 / 런타임
  • 안전성: 프로그래머 책임 / 실패 시 nullptr (포인터) / 예외 (참조)
  • 성능: 오버헤드 없음 / vtable 참조로 런타임 비용 발생
  • 사용 조건: 가상 함수 불필요 / 가상 함수 1개 이상 필요

복기:

  • RTTI = dynamic_cast + typeid — 가상 함수 있는 클래스에서만 동작
  • dynamic_cast 실패 시 포인터면 nullptr, 참조면 std::bad_cast 예외
  • -fno-rtti 컴파일 옵션으로 RTTI 비활성화 가능
  • RTTI 남용은 설계 문제 신호 — 가상 함수로 대체 고려

런타임 에러 유형

런타임 에러는 컴파일은 성공하지만 실행 중에 프로그램이 비정상 종료되거나 잘못된 결과를 내는 오류입니다.

주요 유형:

  • Segmentation Fault — 잘못된 메모리 접근 (널 포인터 역참조, 해제된 메모리 접근)
  • Stack Overflow — 스택 메모리 초과 (무한 재귀, 과도한 지역 변수)
  • Undefined Behavior (UB) — 표준 미정의 동작 (부호 있는 정수 오버플로우, 범위 초과 접근)
  • 메모리 누수 (Memory Leak) — 할당 후 해제 누락 (new 후 delete 없음)
  • Double Free — 같은 메모리 두 번 해제
  • std::exception — out_of_range, bad_alloc, bad_cast
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int* p = nullptr;
*p = 42;               // nullptr 역참조 → segfault

void Infinite() { Infinite(); }   // 무한 재귀 → 스택 오버플로우

void Leak() {
    int* x = new int(42);
    // delete x; 없음 → 메모리 누수
}

// 안전한 패턴: RAII / 스마트 포인터
void Safe() {
    auto x = std::make_unique<int>(42);
}

복기:

  • 런타임 에러는 컴파일러가 잡지 못함 → 실행 중 크래시
  • UB는 크래시 없이 조용히 잘못된 결과를 낼 수 있어 가장 위험
  • RAII / 스마트 포인터로 메모리 누수·double free 방지
  • AddressSanitizer, Valgrind로 메모리 오류 탐지

동적 메모리 할당 (힙 vs 스택)

스택은 함수 호출 시 자동으로 할당·해제되는 메모리 영역이고, 힙은 런타임에 프로그래머가 직접 new/delete로 관리하는 메모리 영역입니다.

힙 vs 스택:

  • 할당 시점: 함수 진입 시 자동 / 런타임 new/malloc 호출 시
  • 해제 시점: 함수 종료 시 자동 / delete/free 호출 시 (수동)
  • 크기: 제한적(1~8MB) / 가용 RAM 한도 내
  • 속도: 빠름 (SP 레지스터 이동) / 느림 (OS 시스템 콜 가능)
  • 수명: 스코프 내 / 명시적 해제 전까지
1
2
3
4
5
6
7
void Example() {
    int  stackVar = 10;          // 스택 — 자동 해제
    int* heapVar  = new int(20); // 힙 — 수동 해제 필요

    auto smart = std::make_unique<int>(30);  // RAII
    delete heapVar;
}

언리얼 엔진과 런타임

언리얼 엔진은 런타임에 다양한 메커니즘을 사용합니다. Cast<T>()는 RTTI 기반 안전한 다운캐스트, IsA<T>()는 타입 검사, 가비지 컬렉터(GC)가 UObject 힙 메모리를 런타임에 자동 관리합니다.

주요 기능:

  • Cast<T>() — UObject 타입 안전 다운캐스트 (dynamic_cast 유사)
  • IsA<T>() — 런타임 타입 검사 (typeid 유사)
  • 가비지 컬렉터 (GC) — UObject 메모리 자동 회수 (delete 자동화)
  • BeginPlay() / Tick() — vtable 동적 디스패치 (virtual 함수 호출)
  • SpawnActor<T>() — 런타임 객체 생성
1
2
3
4
5
6
7
8
9
10
AActor* Actor = GetOwner();
ACharacter* Char = Cast<ACharacter>(Actor);  // 실패 시 nullptr
if (Char) Char->Jump();

if (Actor->IsA<ACharacter>()) { /* ACharacter 파생 타입 */ }

UMyObject* Obj = NewObject<UMyObject>(this);  // GC가 런타임 관리
// delete Obj;  // ← 절대 금지! GC가 자동 해제

AMyActor* Spawned = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass());

복기:

  • Cast<T>() = 언리얼 방식 dynamic_cast; 실패 시 nullptr
  • UObject는 GC가 메모리 관리 → 직접 delete 금지
  • UPROPERTY() 매크로가 있어야 GC가 레퍼런스 추적 가능
  • 런타임 성능 민감 코드(Tick)에서 Cast 남용 주의 — 캐싱 권장

핵심 요약 — 런타임은 컴파일된 프로그램이 OS 위에서 실제로 실행되는 단계이고, 동적 메모리 할당·동적 디스패치·RTTI가 모두 이때 일어난다. constexpr·템플릿·static_assert로 비용을 컴파일 타임으로 옮길 수 있으며, 언리얼의 Cast<T>()와 GC도 결국 런타임 메커니즘이라는 점을 연결해서 정리했다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.