title: "**상속 및 virtual 키워드에 따른 C++ 개체 memory 해부**"
description: "**상속 및 virtual 키워드에 따른 C++ 개체 memory 해부**"
cleanUrl: /sw-engineer/cplusplus-memory-analysis
ogImage: ""
floatFirstTOC: right

COM 구조에 관한 스터디 중 Inteface 지원을 위한 vptr 및 vtable에 관한 내용을 보다, 직접 vptr과 vtable memory layout을 쑤셔보고자 시작. 이 내용을 한두번 본 것도 아닌데 또다시 그냥 지나치려니 영 감질 맛이 나는거다. 하긴, 지금 아니면 언제 이 구조를 뜯어보겠어?

일단, vtable은 함수 포인터의 배열로 이루어진 가상함수 테이블을 의미하며, vptr은 vtable을 가리키는 C++ 개체에 숨겨진 포인터를 뜻한다. 이들 vtable 및 vptr이란 명칭은 관례적으로 그리 사용한다고. 물론 가상함수가 없는(해당 클래스에 virtual 키워드가 없는) 개체는 이들 둘 모두 없다. 따라서 해당 개체의 크기는 vptr의 크기(32bit OS일 경우 4byte)만큼 작아지겠다.

앞으로 보일 예제는 32bit WindowsXP 위의 Visual C++ 2005 버전으로 테스트한 것이다. 참고로, C++ 표준은 vptr의 개체내 위치나 vtable 구현법에 대해 지정하지 않는다고 한다. 따라서 컴파일러에 따라 결과는 달라질 것이다.

확인할 사항은 첫째 vptr 및 vtable의 존재 여부와 다중 상속시 vptr의 개수이다. 확인 방법은 직접 vptr과 vtable의 포인터를 얻어, 이들 포인터를 통해 각 가상 함수를 호출하는 것이다. 다음은 이를 확인해 줄 마루타 클래스들. 순수 추상 클래스까지나 쓸 필요는 없었지만 COM 인터페이스 구현을 모방하기 위해 일부러 이를 선택했다.

class B1
{
public:
    virtual void B1_1() = 0;
    virtual void B1_2() = 0;
};
 
class B2
{
public:
    virtual void B2_1() = 0;
};

class D : public B1, public B2
{
public:
    string var_;

public:
    D() : var_("Hello!") {}

    virtual void B2_1() { cout << "B2" << endl; }

    virtual void D1() { cout << "D1" << endl; }
    virtual void D2() { cout << "D2" << endl; }

    virtual void B1_2() { cout << "B1_2" << endl; }
    virtual void B1_1() { cout << "B1_1" << endl; }
};

별거 없다. 두 개의 base 클래스(B1, B2)와 이들을 다중 상속하면서 string 멤버 변수가 하나 있는 D 클래스이다.

일단 본격적인 테스트를 위한 주요 항목 값을 검사했다(16진수 값 읽기는 워낙 쥐약이라 편의상 주소는 죄다 10진수 타입으로 변환했다).

D d;

cout << "size of d object      :" << sizeof(d) << endl;
cout << "object start address  :" << (int)&d << endl;
cout << "size of var_          :" << sizeof(d.var_) << endl;
cout << "var_ address          :" << (int)&d.var_ << endl;

다음은 위 코드의 결과이다.

size of d object        :40
object start address    :1244972
size of var_            :32
var_ address            :1244980

d 개체 크기는 40 byte에, 크기가 32 byte인 var_의 주소는 개체 시작 주소로부터 8 byte 떨어진 곳에 위치한다. 이제 멤버 변수의 주소를 알고 있으므로 멤버 변수를 직접 부르지 않고도 주소를 통해 간접적으로 변수 값을 확인할 수 있다.

string* pDsVar = reinterpret_cast<string*>((int)(&d) + 8);

cout << "d's var_ member value :"  << *pDsVar << endl;

결과는 "Hello!"로 OK!. 사실 변수 주소를 확인하기 위해 변수 var_의 visiblity scope를 public으로 했지만, private으로 변경하여도 위 코드는 동작한다.

이제 본격적으로 vptr를 얻어본다. 상속한 base 클래스가 두 개이기에 각 vptr의 크기를 더하면 8 byte. 이 공간은 변수 var_의 앞쪽 공간과 일치하며, 변수 var_ 뒤로는 남는 공간이 없기에 바로 이 공간에 각각의 vptr이 위치함을 알 수 있다. 이제는 각각의 vptr이 어떤 base 클래스의 vptr인지만 확인하면 된다. 편의상 B1의 vptr이 먼저 온다고 가정하고 코드를 넣어보았다.