今天查資料的時候發現 VS 編譯器 CL 的一個編譯選項可以查看 C++ 類的內存佈局,非常有用。使用如下,從開始程序菜單找到 Visual Stdio 2008/2005 Command Prompt,選擇 VS 的命令行工具,按如下格式使用:
>cl –d1reportSingleClassLayout[classname] test.cpp
而使用 –d1reportAllClassLayout 則可以查看源文件中所有類及結構體的內存佈局。
其中,classname 爲類名,-d1reportSingleClassLayout[classname] 之間沒有空格。編寫程序測試:
比較奇怪,加上 #include <iostream> 後,測試結構體的時候就會出現很輸出,應該是庫中的類,看起來真麻煩,所以這裏去掉它。
1: //test: >cl Test.cpp /d1reportSingleClassLayout[className]
2: //#include <iostream>
3:
4: //using namespace std;
5:
6: struct S
7: {
8: char x;
9: int y;
10: double z;
11: };
12: class TestClass
13: {
14: private:
15: char y;
16: double z;
17: int x;
18: };
19: //base
20: class Base
21: {
22: private:
23: int x;
24: public:
25: virtual void f1();
26: virtual int g1();
27: };
28: //Derived
29: class Derived: public Base
30: {
31: private:
32: char y;
33: public:
34: virtual float f2();
35: };
36: //Derived2
37: class Derived2: public Base
38: {
39: private:
40: double z;
41: public:
42: virtual void f1();
43: virtual float v2();
44: int f3();
45: };
46: //
47: class Base2
48: {
49: private:
50: int yy;
51: public:
52: virtual void g2();
53: };
54: //多重繼承
55: class Derived3: public Base, public Base2
56: {
57: private:
58: double zz;
59: public:
60: virtual void g3();
61: };
62: //
63: int main()
64: {
65: return 0;
66: }
//測試,1:測試結構體 S:>cl Test.cpp /d1reportSingleClassLayoutS
可以看到,VC 默認情況下,結構體內使用字節對齊,char x, 和 int y 之間填充了 3 個字節的空間。默認情況,VC 對結構體內的字節按最大字節對齊,成員變量之間的順序不同,結構體所佔空間也可能不同。
2. 測度類 TestClass: >cl Test.cpp /d1reportSingleClassLayoutTestClass
同樣可以看到,類 TestClass 中數據成員的按最大數據成員字節對齊,char y 和 double z 之間插入了 7 個字節,double z 和 int x 之間插入了 4 個字節,按 double 型對齊,32 位機器上, sizeof(double) = 8。
3.測試有虛函數的類 Base: >cl Test.cpp /d1reportSingleClassLayoutBase
其中{vfptr}是虛函數表,可以看到,VC 將虛函數表地址放在了對象的頭 4 個字節,接着纔是數據成員。虛函數表是一個數組,裏面存放的是類中虛函數的地址,可以看到虛函數成員的地址是按照聲明的順序存放的。
4.測試子類 Derived:>cl Test.cpp /d1reportSingleClassLayoutDerived
可以看到,基類的虛函數存放在虛表的前面,子類中自己聲明的虛函數按順序存放在後面。
5.測試子類Derived2: >cl Test.cpp /d1reportSingleClassLayoutDerived2
可以看到,子類 Derived2 中重寫了基類 Base 中的虛函數 f1(),因此 Devried2 的虛表中 f1() 的位置被 Derived2 重寫的 f1() 代替,因此便實現了多態。非虛函數地址不存放在虛表中。
6.測試多重繼承的類Derived3: >cl Test.cpp /d1reportSingleClassLayoutDerived3
可以看到VC中對多重繼承的處理,子類 Derived3 的對象中,前 4 個字節存放的是第一個基類的 虛表,然後是第一個基類的數據成員。接着是第 2 個基類的虛表及數據成員。最後纔是自己的數據成員。其中,Derived3::$vftable@Base2@: -8, -8 表示第 2 個基類相對於虛表相對於 Derived3 的偏移量 offset。
//測試結構體的字節對齊,以及 #pragma pack(1), offsetof(struct,number) 的用法。
1: #include <iostream>
2:
3: using namespace std;
4:
5: struct st1
6: {
7: short number;
8: float grade;
9: float grade2;
10: float grade3;
11: char level;
12: }; //20
13:
14: struct st2
15: {
16: char level;
17: short number;
18: float grade;
19: float grade2;
20: float grade3;
21: };//16
22:
23: #pragma pack(1)
24: struct st3
25: {
26: char level;
27: short number;
28: float grade;
29: float grade2;
30: float grade3;
31: }; //15
32: #pragma pack()
33:
34: void TestSizeOf()
35: {
36: cout << __FUNCTION__ << endl;
37:
38: cout << " sizeof(short)= " << sizeof(short) << endl << endl;
39:
40: cout << " sizeof(st1)= " << sizeof (st1) << endl;
41: cout << " offsetof(st1,number) " << offsetof(st1,number) << endl;
42: cout << " offsetof(st1,grade) " << offsetof(st1,grade) << endl;
43: cout << " offsetof(st1,grade2) " << offsetof(st1,grade2) << endl;
44: cout << " offsetof(st1,grade3) " << offsetof(st1,grade3) << endl;
45: cout << " offsetof(st1,level) " << offsetof(st1,level) << endl << endl;
46:
47: cout << " sizeof(st2)= " << sizeof (st2) << endl;
48: cout << " offsetof(st2,level) " << offsetof(st2,level) << endl;
49: cout << " offsetof(st2,number) " << offsetof(st2,number) << endl;
50: cout << " offsetof(st2,grade) " << offsetof(st2,grade) << endl;
51: cout << " offsetof(st2,grade2) " << offsetof(st2,grade2) << endl;
52: cout << " offsetof(st2,grade3) " << offsetof(st2,grade3) << endl << endl;
53:
54: cout << " sizeof(st3)= " << sizeof (st3) << endl;
55: cout << " offsetof(st3,level) " << offsetof(st3,level) << endl;
56: cout << " offsetof(st3,number) " << offsetof(st3,number) << endl;
57: cout << " offsetof(st3,grade) " << offsetof(st3,grade) << endl;
58: cout << " offsetof(st3,grade2) " << offsetof(st3,grade2) << endl;
59: cout << " offsetof(st3,grade3) " << offsetof(st3,grade3) << endl << endl;
60: }
61: int main()
62: {
63: TestSizeOf();
64: return 0;
65: }
其中,VC對結構體中的數據成員默認按照最大成員對齊,#pragma pack(num) 可以設置對齊字節數,可以爲1、2、4、8、16 。也可以使用編譯選項 /Zp[1|2|4|8|16] 修改對齊方式,取消修改用#pragma pack(),如果結構體某成員的 sizeof 大於你設置的,則按你的設置來對齊。注意 offsetof 的用法,可以很容易觀察結構體的內部結構
還可以使用前面所說的 cl –d1reportSingleClassLayout[classname] test.cpp 編譯選項進行相互驗證。