C++ : 가상 함수 테이블 C/C++ 마을


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

덧글

  • 욥츨레 2014/11/16 12:38 # 삭제 답글

    그럼 가상함수 테이블에 의한 함수호출 오버헤드는 switch case 문과 성능이 같나요?
    궁금하네요...
댓글 입력 영역


시계

라운드 시계

위키피디아