C++ 은 다형성, Polymophism 을 지원하기 위해서 virtual 키워드를 제공한다.
부모 클래스 내에서 virtual 로 선언된 함수는 자식 클래스에서 재정의 될 수도 있다.
재정의 되지 않는다면, 호출시 부모 클래스의 함수가 호출되고, 재정의 된다면 호출 시 자식 클래스의 함수가 호출된다.
Base, Derived 두개의 클래스가 있다고 가정 해 보자. Base 클래스는 Derived 클래스의 부모 클래스다.
각각 start 란 virtual 함수를 가지고 있다고 하자. virtual 함수는 재정의(Override) 될 것이다.
Base* pBase = new Derived();
pBase->start();
컴파일러는 pBase 가 Derived 타입인지, Base 타입인지 알지 못한다.
즉, start 를 호출하는 런타임 시점까지는 Derived 클래스의 재정의된 start 가 호출될지 부모 클래스의 start 가 호출될지 모른다.
그렇다면, 프로그램은 어떻게 런타임에 자신의 클래스에 맞는 함수를 찾아가서 호출하는 것일까?
답은 가상 함수 테이블(vftbl) 에 있다.
클래스 내에 virtual 함수가 존재하고, 이 virtual 함수가 상위 클래스의 함수를 override 하거나, 하위 클래스에 의해 override 된다면 컴파일러는 클래스 내에 이 함수에 대한 가상 함수 테이블을 생성한다.
아래의 그림을 보자. High, Middle, Low 클래스가 있고, 상속 관계이며 오버라이딩된 함수를 가지고 있다. (누르면 커짐)

아래는 소스코드.
01: // Sample.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
02: //
03:
04: #include "stdafx.h"
05:
06: #include <iostream>
07:
08: class High
09: {
10: int a;
11:
12: public:
13: virtual void func1(void)
14: {
15: std::cout << "High::func1" << std::endl;
16: }
17:
18: virtual void func2(void)
19: {
20: std::cout << "High::func2" << std::endl;
21: }
22: };
23:
24: class Middle : public High
25: {
26: int b;
27:
28: public:
29: virtual void func1(void)
30: {
31: std::cout << "Middle::func1" << std::endl;
32: }
33:
34: virtual void func3(void)
35: {
36: std::cout << "Middle::func3" << std::endl;
37: }
38: };
39:
40: class Low : public Middle
41: {
42: int c;
43:
44: public:
45: virtual void func1(void)
46: {
47: std::cout << "Low::func1" << std::endl;
48: }
49:
50: virtual void func3(void)
51: {
52: std::cout << "Low::func3" << std::endl;
53: }
54:
55: virtual void func4(void)
56: {
57: std::cout << "Low::func4" << std::endl;
58: }
59: };
60:
61: int _tmain(int argc, _TCHAR* argv[])
62: {
63:
64: High* cHigh = new High();
65: cHigh->func1();
66:
67: Middle* cMiddle = new Middle();
68: cMiddle->func3();
69:
70: Low* cLow = new Low();
71: cLow->func3();
72:
73: return 0;
74: }
75:
클래스 내에 가상 함수 테이블이 포함되면, 클래스의 첫 4바이트에 가상 함수 테이블 주소가 추가된다. 아래는 VC에서 확인한 스크린 샷.

위의 조사식에서 Low 클래스의 인스턴스인 cLow 의 메모리 주소는 0x00397bd8 임을 알 수 있다. 이 메모리 값을 확인 해 보면
아래와 같이 0x00447941 이다.

이 값은 Low 클래스의 가상 함수 테이블의 주소 0x417944 와 같은 값이다. 아래의 스크린샷에서 확인할 수 있다.

Low 클래스의 vftable 의 주소는 0x00417944 다. 이러한 가상 함수 테이블은 인스턴스마다 존재하면 메모리가 낭비되므로 테이블을 클래스 정보로 두고 각각의 인스턴스는 포인터 값으로 이 가상 함수 테이블을 가리키고 있다.
위의 조사식 스크린샷을 다시 한번 보자.

Low 클래스는 func1, func2, func3, func4 를 가지고 있다.
func1 은 High 클래스와 Middle 클래스의 func1 을 오버라이딩 한 것이고
func2 는 Low 클래스 내에는 없지만, 상속받은 High 클래스 내에 존재하므로 호출 할 수 있다.
func3 은 Middle 클래스의 func3 을 오버라이딩 한 것이다.
func4 는 가상함수로 선언되긴 했지만, 실제 오버라이딩 한 함수도 아니고, 오버라이딩 된 것도 아니므로 가상함수 테이블에 포함되지 않는다.
조사식에서는 cLow 의 가상 함수 테이블에 2개의 값, Low::func1 과 High::func2 밖에 안나왔지만, 사실 Middle 클래스의 func3 함수를 오버라이딩한 Low::func3 도 가상 함수 테이블에 존재해야 한다. 오버라이딩 했기 때문이다.
실제로 존재하는지 확인 해 보자.

위의 그림은 cLow 클래스의 첫 4바이트가 가리키는 0x00417944 를 확인 한 것이다.
보면 알겠지만, 가상함수 테이블에 3개의 함수가 존재한다.
Low::func1, High::func2, Low::func3. 조사식과 비교해 보면 올바른 주소임을 확인 할 수 있다.
2011. 08. 18
By. Anster
덧글
궁금하네요...