[DAY30] Test Recap(2) : Polymorphism, Virtual Table

베리투스·2025년 9월 15일

TIL: Today I Learned

목록 보기
31/93

오늘은 C++의 꽃이라 불리는 다형성에 대해 깊이 파고들었다. 모의 면접을 통해 내가 놓치고 있던 중요한 디테일들을 발견했다. 특히 가상 함수 테이블(vtable)이 언제 생성되고, 사용하는 데 어떤 비용이 드는지 정확히 알게 되었다. 단순히 '어떻게 쓰는가'를 넘어 '어떻게 동작하는가'를 이해하는 것이 얼마나 중요한지 깨달은 하루였다.


📌 목표

  • 동적 바인딩의 구체적인 동작 원리 설명하기
  • 가상 함수 테이블(vtable)의 생성 시점과 개수 정확히 알기
  • 가상 함수 사용 시 발생하는 성능 비용(오버헤드) 이해하기

📖 이론

1. 동적 바인딩, 머릿속으로 그려보기

이전까지는 '부모 포인터로 자식 객체를 가리키면 알아서 자식의 오버라이딩된 함수가 호출된다' 정도로만 알고 있었다. 오늘 그 '알아서'의 과정을 명확히 정리했다.

  1. Animal* p = new Dog(); 와 같이 기반 클래스 포인터가 파생 클래스 객체를 가리킨다.
  2. Dog 객체 내부에는 가상 함수 테이블 포인터(vptr)가 숨겨져 있다.
  3. vptrDog 클래스의 가상 함수 테이블(vtable)을 가리킨다.
  4. p->speak(); 가 호출되면, 프로그램은 런타임에 p가 가리키는 객체의 vptr을 따라간다.
  5. vptr이 가리키는 Dogvtable에 가서 speak() 함수의 실제 주소를 찾는다.
  6. 결과적으로 Dog 클래스에 오버라이딩된 speak() 함수가 호출된다. ✨

2. 가상 함수 테이블(vtable)의 진실

이 부분에서 내가 가장 많이 착각하고 있었다.

  • 생성 시점: 런타임에 객체가 만들어질 때마다 생성되는 것이 아니다. vtable은 컴파일 타임에, 가상 함수를 가진 클래스별로 단 하나씩 만들어진다.
  • 객체와의 관계: 객체는 vtable 자체를 가지는 게 아니다. 대신 자신의 클래스에 맞는 vtable을 가리키는 포인터(vptr)만 멤버로 가질 뿐이다. 모든 Dog 객체는 똑같은 Dog 클래스의 vtable을 가리킨다.
  • 성능 비용: 컴파일 시간이 늘어나는 것이 아니었다. 진짜 비용은 두 가지다.
    • 런타임 비용: 일반 함수 호출과 달리 vptr을 통해 vtable을 거쳐 함수 주소를 찾는, 즉 한 단계 더 거쳐서(간접 참조) 호출하므로 아주 약간의 실행 시간 오버헤드가 발생한다.
    • 메모리 비용: 가상 함수가 있는 클래스의 모든 객체는 vptr을 가져야 하므로, 객체의 크기가 포인터 크기만큼(보통 8바이트) 커진다.

⚠️ 실수

  • 가상 함수 테이블은 런타임에 객체별로 생성된다고 착각했다.

    • (X) 객체가 생성될 때마다 vtable이 생긴다.
    • (O) vtable은 컴파일 타임에 클래스별로 하나만 생성된다. 객체는 vtable을 가리키는 포인터(vptr)만 갖는다.
  • 가상 함수 사용의 단점을 컴파일 시간 증가라고 잘못 생각했다.

    • (X) 가상 함수가 많으면 컴파일이 느려진다.
    • (O) 런타임에 vtable을 참조하는 과정 때문에 약간의 성능 저하가 있고, 객체마다 vptr이 추가되어 메모리 사용량이 늘어난다.

✅ 핵심 요약

개념설명비고
동적 바인딩 (과정)객체의 vptr클래스의 vtable실제 함수 주소 순서로 런타임에 호출할 함수를 결정한다.vptr: virtual pointer
vtable: virtual table
가상 함수 테이블 (생성)컴파일 타임에 가상 함수를 가진 클래스별로 1개 생성된다.객체별로 생성되는 것이 절대 아님!
가상 함수 (비용)1. 런타임 오버헤드: 간접 호출로 인한 약간의 성능 저하
2. 메모리 오버헤드: 객체 크기 증가 (vptr 만큼)
컴파일 시간과는 거의 무관하다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글