Virtual Function과 Virtual Table(vtable)의 동작 원리 정리
이번 시간에는 C++에서 Dynamic Polymorphism(동적 다형성)을 구현하는 핵심 기술인 Virtual Function과 Virtual Table(vtable)의 동작 원리에 대해 알아보았습니다.
1. Virtual Function 사용 시 객체 크기의 변화
기본적인 상속 구조에서는 클래스가 가진 멤버 변수의 크기만큼 객체 크기가 결정됩니다.
예를 들어:
class Animal
{
double height;
};
class Cat : public Animal
{
double weight;
};
이라면:
- Animal
- double 1개 → 8Byte
- Cat
- Animal의 height
- Cat의 weight
- double 2개 → 16Byte
가 됩니다.
하지만 Virtual Function을 추가하면 객체 크기가 증가합니다.
virtual void Speak();
를 추가하면:
- Animal: 8Byte → 16Byte
- Cat: 16Byte → 24Byte
로 증가합니다.
증가하는 이유: vptr 추가
Virtual Function을 사용하면 컴파일러는 객체 내부에 추가적인 포인터 정보를 삽입합니다.
이 포인터를:
vptr (Virtual Pointer)
이라고 합니다.
64bit 시스템에서는 포인터 크기가:
8Byte
이므로 객체마다 8Byte가 추가됩니다.
결과:
Animal
데이터크기
| vptr | 8Byte |
| height | 8Byte |
| 합계 | 16Byte |
Cat
데이터크기
| vptr | 8Byte |
| height | 8Byte |
| weight | 8Byte |
| 합계 | 24Byte |
2. Virtual Table(vtable)의 구조
vptr은 특정 테이블을 가리키는데, 이것이:
Virtual Table(vtable)
입니다.
클래스마다 각각의 vtable이 생성됩니다.
예:
Animal vtable
----------------
Animal::Speak()
Animal::~Animal()
Cat vtable
----------------
Cat::Speak()
Cat::~Cat()
즉:
- Animal 객체 → Animal vtable 가리킴
- Cat 객체 → Cat vtable 가리킴
이라는 구조가 됩니다.
3. Dynamic Polymorphism 동작 과정
다음 코드를 생각해 볼 수 있습니다.
Animal* ptr = new Cat();
ptr->Speak();
겉으로 보면:
- 포인터 타입: Animal*
- 생성된 객체: Cat
입니다.
메모리 구조:
Stack
ptr
|
|
v
Heap
+----------------+
| vptr ----------|-----> Cat vtable
| height |
| weight |
+----------------+
ptr->Speak() 호출 과정:
1단계
포인터가 가리키는 객체 확인
ptr → Cat 객체
2단계
객체 내부의 vptr 확인
vptr → Cat vtable
3단계
vtable에서 함수 주소 확인
Cat vtable
|
└── Cat::Speak()
4단계
Cat의 Speak 실행
출력:
Meow
즉 함수 호출이 컴파일 시간에 결정되는 것이 아니라:
실행 시간(Runtime)에 실제 객체 타입을 확인하고 호출할 함수를 결정
하게 됩니다.
이것이 Dynamic Polymorphism입니다.
4. Animal 객체를 생성하는 경우
만약:
Animal* ptr = new Animal();
이라면:
ptr
|
v
Animal 객체
vptr → Animal vtable
구조가 됩니다.
따라서:
ptr->Speak();
호출 시:
Animal::Speak()
가 실행됩니다.
5. Virtual Destructor와 vtable
이전 시간에서:
Base 클래스의 Destructor는 virtual로 선언해야 한다
라고 배웠습니다.
이유는 Destructor 역시 Virtual Function이기 때문입니다.
예:
virtual ~Animal();
을 추가하면:
Animal vtable:
Animal vtable
----------------
Animal::Speak()
Animal::~Animal()
Cat vtable:
Cat vtable
----------------
Cat::Speak()
Cat::~Cat()
형태가 됩니다.
따라서:
Animal* ptr = new Cat();
delete ptr;
를 실행하면:
- Cat Destructor 호출
- Animal Destructor 호출
순서로 정상적으로 객체가 제거됩니다.
핵심 정리
개념설명
| Virtual Function | 런타임 함수 선택을 가능하게 하는 함수 |
| vptr | 객체 내부에 추가되는 Virtual Table 포인터 |
| vtable | Virtual 함수 주소를 저장하는 테이블 |
| Dynamic Polymorphism | 실행 중 실제 객체 타입에 따라 함수 호출 |
| Virtual Destructor | Base 포인터로 Derived 객체 삭제 시 필수 |
전체 동작 흐름
Animal* ptr = new Cat();
ptr
|
v
Cat Object
+-------------+
| vptr |
| height |
| weight |
+-------------+
|
v
Cat vtable
+-------------+
| Cat::Speak |
| Cat::~Cat |
+-------------+
결론적으로 C++의 Dynamic Polymorphism은 마법처럼 동작하는 것이 아니라,
- 객체 내부의 vptr
- 클래스별 vtable
- vtable 안의 함수 주소
를 이용해서 런타임에 호출할 함수를 결정하는 구조입니다.
이 원리를 이해하면 이후에 배우게 되는 Object Slicing, Dynamic Cast, Multiple Inheritance, ABI 구조 등의 고급 C++ 개념을 이해하는 기반이 됩니다.
'Fundamental of CS > : : C++' 카테고리의 다른 글
| [Inheritance] Multiple Inheritance (0) | 2024.07.16 |
|---|---|
| [Inheritance] Pure Virtual Function (0) | 2024.07.16 |
| [Inheritance] Virtual Function (0) | 2024.07.16 |
| [Inheritance] C++ 상속(Inheritance) Intro (0) | 2024.07.16 |
| [Inheritance] C++ Inheritance (0) | 2024.07.16 |