讀書筆記: Inside the C++ Object Model (1)

February 9th, 2011

最近重讀了一下 Stanley Lippman 的 Inside the C++ Object Model (有中譯本,侯俊傑譯的 C++ 對象模型),這書有點歷史了,所以裡頭很多東西其實不能全信,要做過實驗才知道。這些實驗還蠻有價值的,我會把我做的實驗整理一下貼上來,以免忘記 😀 (這是中年大叔的通病) 這一系列的文章都是以 Microsoft Visual C++ 2010 和 Debugging Tools for Windows x86 做出來的。

首先是對原書圖 1.3 的實驗,程式碼如下
point.h

// point.h
#include 

class Point
{
public:
    Point(float xval);
    virtual ~Point();
    
    float x() const;
    static int PointCount();
    
protected:
    virtual std::ostream& print(std::ostream& os) const;   

    float _x;
    static int _point_count;
};

point.cpp
// point.cpp
#include "point.h"

Point::Point(float xval) : _x(xval)
{
    ++_point_count;
}

Point::~Point()
{
    --_point_count;
}

float Point::x() const
{
    return _x;
}

int Point::PointCount()
{
    return _point_count;
}

std::ostream& Point::print(std::ostream& os) const
{
    os << _x;
    return os;
}
test1.cpp
#include 
#include "point.h"

using namespace std;

// instantiate the global static var
int Point::_point_count;
    
int main()
{
    Point* pPoint = new Point(2);
    cout << Point::PointCount() << endl;
    cout << sizeof(Point) << endl;
    cout << pPoint->x() << endl;
    delete pPoint;
    return 0;
}

不想太麻煩所以直接用命令列來編譯,我的環境可以直接調用 cl 和 windbg

cl /EHsc /Zi /Fd test1.cpp point.cpp
windbg test1.exe

在 windbg 下可以直接打開 test1.cpp,在第 12 行設中斷點,然後執行這個程式。停下來時我們就可以好好觀察一下 pPoint 的結構了

0:000> dt pPoint
Local var @ 0x3cfe28 Type Point*
0x00112008 
   +0x000 __VFN_table : 0x012a84f4 
   +0x004 _x               : 2 
   =012b1e00 Point::_point_count : 1

和原文圖 1.3 不同的是,vtable 是第一個而不是最後一個,這是 Microsoft 發明的做法,有興趣可觀察一下,似乎 gcc 也是這麼做的。接下來看看 vtable 裡有什麼

0:000> dds 0x012a84f4 
012a84f4  012710f5 test1!ILT+240(??_GPointUAEPAXIZ)
012a84f8  01271258 test1!ILT+595(?printPointMBEAAV?$basic_ostreamDU?$char_traitsDstdstdAAV23Z)
012a84fc  00000000
...

ILT? 這是什麼東西呢?其實是 Incremental Link Table 的縮寫,它加了一層 indirection 把真正的 function 放到 ILT 去,這樣 linker 就不必每次都重新安排函數的位置,但這對我們做實驗是很不利的。所以呢,還是得乖乖的用 make file 或 vcproj。身為老派硬漢,當然要用硬漢用的 nmake
cppom.mak

CC      = cl
CFLAGS	= /EHsc /Zi /Fd /c
LD      = link
LDARGS  = /INCREMENTAL:NO /DEBUG
RM	    = del /q

.cpp.obj::
    $(CC) $(CFLAGS) $<

test1.exe: test1.obj point.obj
	$(LD) test1.obj point.obj $(LDARGS) /PDB:test1.pdb

clean:
	$(RM) *.exe *.obj *.pdb *.idb *.ilk *.bak

all: test1.exe

用 nmake 造出新的 test1.exe 之後,重複以上的步驟,再來看看我們的 vtable

0:000> dt pPoint
Local var @ 0x37fef0 Type Point*
0x001e2008 
   +0x000 __VFN_table : 0x00dd95c8 
   +0x004 _x               : 2 
   =00de0820 Point::_point_count : 1

0:000> dds 0x00dd95c8
00dd95c8  00db5a50 test1!Point::`scalar deleting destructor'
00dd95cc  00db5a20 test1!Point::print [c:\users\arthur\desktop\research\point.cpp @ 24]
...

看起來順眼多了,不過啥是 `scalar deleting destructor' 啊?其實這是 VC 自動幫我們的 destructor 包的一層殼。
反組譯實驗

0:000> ln 00db5a50 
(00db5a50)   test1!Point::`scalar deleting destructor'   |  (00db5a7c)   test1!std::_Iterator_base0::_Adopt
Exact matches:
    test1!Point::`scalar deleting destructor' (void)
0:000> u 00db5a50 00db5a7b
test1!Point::`scalar deleting destructor':
00db5a50 55              push    ebp
00db5a51 8bec            mov     ebp,esp
00db5a53 51              push    ecx
00db5a54 894dfc          mov     dword ptr [ebp-4],ecx
00db5a57 8b4dfc          mov     ecx,dword ptr [ebp-4]
00db5a5a e861ffffff      call    test1!Point::~Point (00db59c0)
00db5a5f 8b4508          mov     eax,dword ptr [ebp+8]
00db5a62 83e001          and     eax,1
00db5a65 740c            je      test1!Point::`scalar deleting destructor'+0x23 (00db5a73)
00db5a67 8b4dfc          mov     ecx,dword ptr [ebp-4]
00db5a6a 51              push    ecx
00db5a6b e854290000      call    test1!operator delete (00db83c4)
00db5a70 83c404          add     esp,4
00db5a73 8b45fc          mov     eax,dword ptr [ebp-4]
00db5a76 8be5            mov     esp,ebp
00db5a78 5d              pop     ebp
00db5a79 c20400          ret     4

圖 1.3 中提到,vtable 的第 0 個 entry 應指向 type info,但我們看的結果似乎不是如此,如果往上捲個 4 byte 會如何呢?

0:000> dds 0x00dd95c4
00dd95c4  00ddcadc test1!Point::`RTTI Complete Object Locator'
00dd95c8  00db5a50 test1!Point::`scalar deleting destructor'
00dd95cc  00db5a20 test1!Point::print [c:\users\arthur\desktop\research\point.cpp @ 24]
...

`RTTI Complete Object Locator' 應是我們要找的東西,那又是另一個故事了,拜了一下咕狗大神,07 年的黑帽有專文討論,這裡就不嚕囌。所以到目前為止,我們的實驗證明了幾件事實:

  • VC 就如圖 1.3 中所畫的,會自己安排 static variable, static member function 以及 non-virtual member function 的位置
  • 每個 object instance 都內含指向 vtable 的指標 (如果有 vtable 的話)
  • vtable 的位置通常與圖 1.3 中畫的相反,它在最開始的位置。RTTI 資訊事實上也不一定放在 vtable 的第一個指向位置,只要 compiler 推的出來就可以

C++/C#, 技術文章 | Comments Jump to the top of this page

Comments are closed.

隨便寫寫大家隨便看看的不出名小格子

舊文索引

站內管理