使用 CL 編譯器選項查看 C++ 類內存佈局

     今天查資料的時候發現 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

Struct

可以看到,VC 默認情況下,結構體內使用字節對齊,char x, 和 int y 之間填充了 3 個字節的空間。默認情況,VC 對結構體內的字節按最大字節對齊,成員變量之間的順序不同,結構體所佔空間也可能不同。

2. 測度類 TestClass: >cl Test.cpp /d1reportSingleClassLayoutTestClass

testClass

同樣可以看到,類 TestClass 中數據成員的按最大數據成員字節對齊,char y 和 double z 之間插入了 7 個字節,double z 和 int x 之間插入了 4 個字節,按 double 型對齊,32 位機器上, sizeof(double) = 8。

3.測試有虛函數的類 Base: >cl Test.cpp /d1reportSingleClassLayoutBase

Base

其中{vfptr}是虛函數表,可以看到,VC 將虛函數表地址放在了對象的頭 4 個字節,接着纔是數據成員。虛函數表是一個數組,裏面存放的是類中虛函數的地址,可以看到虛函數成員的地址是按照聲明的順序存放的。

4.測試子類 Derived:>cl Test.cpp /d1reportSingleClassLayoutDerived

Derived

可以看到,基類的虛函數存放在虛表的前面,子類中自己聲明的虛函數按順序存放在後面。

5.測試子類Derived2: >cl Test.cpp /d1reportSingleClassLayoutDerived2

Derived2

可以看到,子類 Derived2 中重寫了基類 Base 中的虛函數 f1(),因此 Devried2 的虛表中 f1() 的位置被 Derived2 重寫的 f1() 代替,因此便實現了多態。非虛函數地址不存放在虛表中。

6.測試多重繼承的類Derived3: >cl Test.cpp /d1reportSingleClassLayoutDerived3

Derived3

可以看到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 的用法,可以很容易觀察結構體的內部結構

sizeof

還可以使用前面所說的 cl –d1reportSingleClassLayout[classname]  test.cpp 編譯選項進行相互驗證。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章