목차
- 01. Intro
- 02. 가상 함수
- 가상 함수 테이블 포인터
- 가상 함수 테이블
- 03. 마무리
- 04. 연관 내용
01. Intro
지난 시간에 오버라이딩 종류인 [정적 오버라이딩]과 [동적 오버라이딩]에 대해서 알아봤습니다.
동적 오버라이딩을 적용하려면, 함수를 선언할 때, virtual 키워드를 같이 사용해 가상 함수로 만들었습니다.
오늘은 가상 함수를 사용하면, 어떤 원리로 의해서 원본 클래스의 함수가 실행되는지 알아보겠습니다.
02. 가상 함수
가상 함수는 virtual 키워드를 선언한 함수를 말합니다.
가상 함수를 실행할 때, 가상 함수 테이블(vtable)과 가상 함수 테이블 포인터(vptr)를 통해서 원본 클래스의 함수를 찾습니다.
이를 통해 어떻게 작동하는지 자세히 살펴보겠습니다.
가상 함수 테이블 포인터
#include <iostream>
using namespace std;
class Sport
{
public:
virtual void Play() const
{
cout << "Sport Game Start!" << endl;
}
int _game = 0;
};
class Soccer : public Sport
{
public:
virtual void Play() const
{
cout << "Soccer Game Start!" << endl;
}
int _ball = 5;
};
int main()
{
Soccer* soccer = new Soccer();
Sport* sport = soccer;
sport->Play();
}
현재, sport 포인터에는 Soccer 클래스의 객체에 대한 주소값을 가지고 있습니다.
원본의 클래스 함수로 찾아가려면, Soccer 클래스 객체는 현재 자신의 클래스 타입이 무엇인지 표기해야 알 수 있습니다.
따라서, 이를 표기해 주는 것이 가상 함수 테이블 포인터(vptr)입니다.
vptr는 해당 클래스의 vtable에 대한 주소값을 가지고 있습니다.
vptr은 항상 클래스 객체가 할당받은 영역 중 첫 번째에 위치합니다.
정말로 vptr이 첫 번째에 위치하는지 메모리를 통해 확인해 보겠습니다.
가상 함수 사용한 클래스 메모리 확인
위 코드를 실행한 결과를 바탕으로 확인해 보겠습니다.

메모리 주소에서 &sport로 sport의 변수값을 확인할 수 있습니다.
sport는 포인터 변수로 가운데에 위치한 값은 Soccer 객체가 위치한 주소값입니다.

그래서 해당 주소값을 입력하면, Soccer 객체가 위치한 메모리 주소로 이동합니다.
두 번째 줄은 _game 변수에 대한 값인 0이 저장되어 있습니다.
세 번째 줄에는 자식 클래스의 멤버 변수인 _ball에 대한 값인 5가 저장되어 있습니다.
따라서, 첫 번째 줄이 가상 함수 테이블 포인터인 것을 확인할 수 있습니다.
하지만, 조금 더 정확하게 알기 위해서 가상 함수가 없는 클래스에 대한 객체는 메모리에 어떻게 할당되어 있는지 알아보겠습니다.
두 상황에 대한 차이점을 보고 정말로 첫 번째 줄이 vptr인지 확인해 보겠습니다.
가상 함수 사용하지 않은 클래스 메모리 확인
#include <iostream>
using namespace std;
class Sport
{
public:
void Play() const
{
cout << "Sport Game Start!" << endl;
}
int _game = 0;
};
class Soccer : public Sport
{
public:
void Play() const
{
cout << "Soccer Game Start!" << endl;
}
int _ball = 5;
};
int main()
{
Soccer* soccer = new Soccer();
Sport* sport = soccer;
sport->Play();
}
이전 코드에서 virtual 키워드를 모두 제거해서 함수를 선언한 뒤, 빌드를 진행했습니다.

&soccer로 soccer 변수에 위치한 주소값을 확인합니다.
( 프로그램을 실행할 때마다 메모리 주소값은 변경될 수 있습니다. )

Soccer 클래스의 객체가 동적 할당된 메모리 주소로 이동합니다.
위 사진을 보면, 첫 번째 줄은 _game 변수의 값인 0이 저장되어 있습니다.
두 번째 줄은 _ball 변수의 값인 5가 저장되어 있습니다.
가상 함수 테이블 포인터 위치에 대한 결론
따라서, 가상 함수가 있는 클래스에 대한 객체의 메모리 영역 중 첫 번째 줄은 vtable을 가리키는 포인터임을 알 수 있습니다.
가상 함수 테이블 포인터는 누가 초기화할까?
vptr는 생성자가 초기화를 합니다.
아래 코드를 통해 확인해 보겠습니다.
#include <iostream>
using namespace std;
class Sport
{
public:
virtual void Play() const
{
cout << "Sport Game Start!" << endl;
}
int _game = 0;
};
class Soccer : public Sport
{
public:
virtual void Play() const
{
cout << "Soccer Game Start!" << endl;
}
int _ball = 5;
};
int main()
{
Soccer soccer;
}
Soccer 클래스에 대한 객체를 생성했을 때, 해당 객체의 메모리 주소와 어셈블리 코드를 보면 아래처럼 실행됩니다.

처음에는 soccer 객체에 아무런 값도 할당되지 않았습니다.


Sport의 생성자에 의해서 Sport 클래스에 대한 vtable 주소값이 먼저 저장됩니다.


그 후, Soccer 생성자에 의해서 Soccer 클래스에 대한 vtable 주소값이 덮어쓰기 합니다.

생성자 코드가 실행 다 되면, 위 사진처럼 값을 최종적으로 가지게 됩니다.
가상 함수 테이블
vptr은 가상 함수 테이블(vtable)의 주소값이 저장되어 있습니다.
vtable에는 해당 클래스에 대한 가상 함수들의 위치가 저장되어 있습니다.
vtable은 컴파일러가 생성하며, 객체들은 vptr로 vtable을 참조하는 방식으로 작동합니다.
즉, Soccer 클래스의 vtable에는 Soccer 클래스의 Play() 가상 함수에 대한 주소가 저장되어 있습니다.
또한, Sport 클래스의 vtable에는 Sport 클래스의 Play() 가상 함수에 대한 주소가 저장되어 있습니다.
가상 함수 테이블은 몇 개일까?
가상 함수 테이블은 클래스당 한 개만 존재합니다.
아래 코드를 보면서 직접 확인해 보겠습니다.
#include <iostream>
using namespace std;
class Sport
{
public:
virtual void Play() const
{
cout << "Sport Game Start!" << endl;
}
int _game = 0;
};
class Soccer : public Sport
{
public:
void Play() const
{
cout << "Soccer Game Start!" << endl;
}
int _ball = 5;
};
int main()
{
Soccer* soccer1 = new Soccer();
Soccer* soccer2 = new Soccer();
return 0;
}
이번에는 Soccer 클래스에 대한 포인터 변수 2개에 각각 동적 할당을 했습니다.
실행 후, soccer1과 soccer2에 대해 메모리를 확인한 결과 다음과 같았습니다.
![]() |
![]() |
| soccer1 포인터 변수에 대한 메모리 주소 확인 | soccer1 포인터 변수가 가리키는 메모리 주소로 접근 |
![]() |
![]() |
| soccer2 포인터 변수에 대한 메모리 주소 확인 | soccer2 포인터 변수가 가리키는 메모리 주소로 접근 |
메모리 주소를 통해 확인하면, soccer1이 가리키는 객체와 soccer2가 가리키는 객체는 서로 다릅니다.
하지만, 두 객체의 첫 번째 줄은 서로 같은 주소값을 가지고 있습니다.
이를 통해서 vtable은 하나만 존재한다 것을 알 수 있습니다.
또한, 가상 함수를 가진 클래스 객체들마다 vptr을 가진다는 것을 알 수 있습니다.
03. 마무리
다음 시간에는 가상 함수의 형태 중 하나인 순수 가상 함수 및 추상 클래스에 대해서 알아보겠습니다.
04. 연관 내용
- [C++] 오버라이딩 - 1. 정적 & 동적
[C++] 오버라이딩 - 1. 정적 & 동적
목차01. Intro02. 오버라이딩정적 오버라이딩동적 오버라이딩03. 마무리04. 연관 내용01. Intro오늘은 클래스를 상속받았을 때, 사용할 수 있는 함수 오버라이딩에 대해 알아보겠습니다. 02. 오버라이
epidemic-barcode.tistory.com
- [C++] 오버라이딩 - 3. 순수 가상 함수 및 추상 클래스
[C++] 오버라이딩 - 2. 가상 함수 테이블 & 포인터
목차01. Intro02. 가상 함수가상 함수 테이블 포인터가상 함수 테이블 03. 마무리04. 연관 내용01. Intro지난 시간에 오버라이딩 종류인 [정적 오버라이딩]과 [동적 오버라이딩]에 대해서 알아봤습니다
epidemic-barcode.tistory.com
읽어주셔서 감사합니다.
틀린 내용 지적은 언제나 환영입니다!
'게임 개발 > C++' 카테고리의 다른 글
| [C++] 오버라이딩 - 4. 가상 소멸자 (0) | 2025.02.27 |
|---|---|
| [C++] 오버라이딩 - 3. 순수 가상 함수 (0) | 2025.02.26 |
| [C++] 오버라이딩 - 1. 정적 & 동적 (0) | 2025.02.24 |
| [C++] Class vs Struct (0) | 2025.02.23 |
| [C++] 컴파일 과정 - 번외. 심볼과 심볼 테이블 (2) | 2025.02.22 |



