<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      厚積薄發
      海納百川,有容乃大
      關于 C++中new背后的行為, 以前已經寫過一篇了 理解C++中new背后的行為, 但是里面也只是泛泛而談,沒有真憑實據, 下面我們從匯編的角度看C++編譯器究竟在背后干了什么?

      我們的代碼很簡單, 如下:
      #include <iostream>

      class A
      {
      public:
      virtual void print()
      {
      std::cout << 10;
      }

      virtual ~A()
      {
      std::cout << "~A()";
      }

      };

      class B: public A
      {
      public:
      virtual void print()
      {
      std::cout << 100;
      }
      };

      int _tmain(int argc, _TCHAR* argv[])
      {
      A* p = new B();
      p->print();
      delete p;

      return 0;
      }

      我用WinDbg可以看到main函數生成的匯編代碼如下: 

      NewTest!wmain:
      00aa1020 56              push    esi
      00aa1021 6a04            push    4 
      00aa1023 e8b4030000      call    NewTest!operator new (00aa13dc) //調用operator new分配大小為4字節的空間
      00aa1028 83c404          add     esp,4
      00aa102b 85c0            test    eax,eax
      00aa102d 740a            je      NewTest!wmain+0x19 (00aa1039)
      00aa102f c7005421aa00    mov     dword ptr [eax],offset NewTest!B::`vftable' (00aa2154) //將虛表地址寫入對象地址的頭4個字節(虛表指針)
      00aa1035 8bf0            mov     esi,eax
      00aa1037 eb02            jmp     NewTest!wmain+0x1b (00aa103b)
      00aa1039 33f6            xor     esi,esi
      00aa103b 8b06            mov     eax,dword ptr [esi]
      00aa103d 8b10            mov     edx,dword ptr [eax]
      00aa103f 8bce            mov     ecx,esi
      00aa1041 ffd2            call    edx //調用虛表內的第一個函數print
      00aa1043 8b06            mov     eax,dword ptr [esi]
      00aa1045 8b5004          mov     edx,dword ptr [eax+4]
      00aa1048 6a01            push    1
      00aa104a 8bce            mov     ecx,esi
      00aa104c ffd2            call    edx //調用虛表內的第二個函數(析構函數)
      00aa104e 33c0            xor     eax,eax
      00aa1050 5e              pop     esi
      00aa1051 c3              ret
      00aa1052 cc              int     3

      從上面代碼中我們可以看到我們構造的B對象一共只有4個字節,而這四個字節包含的就是對象的虛表指針,對于C++對象內存布局, 對于C++對象的內存布局,可以看我這篇 探索C++對象模型。同時我們可以看到, C++里確實是通過虛表來實現多態的。

      上面的代碼也告訴了我們為什么不能在構造函數里通過調用虛函數實現多態? 因為虛表是在最終派生類的構造函數中生成的的, 執行基類構造函數時虛表都還沒有生成。

      接下來我們看看operator new背后的行為:
      0:000> u 00aa13dc
      NewTest!operator new:
      00aa13dc ff25cc20aa00    jmp     dword ptr [NewTest!_imp_??2YAPAXIZ (00aa20cc)]

      里面是一個直接跳轉:
      0:000> u poi(00aa20cc) L10
      MSVCR90!operator new:
      74603e99 8bff            mov     edi,edi
      74603e9b 55              push    ebp
      74603e9c 8bec            mov     ebp,esp
      74603e9e 83ec0c          sub     esp,0Ch
      74603ea1 eb0d            jmp     MSVCR90!operator new+0x17 (74603eb0)
      74603ea3 ff7508          push    dword ptr [ebp+8]
      74603ea6 e859dcfbff      call    MSVCR90!_callnewh (745c1b04)
      74603eab 59              pop     ecx
      74603eac 85c0            test    eax,eax
      74603eae 740f            je      MSVCR90!operator new+0x26 (74603ebf)
      74603eb0 ff7508          push    dword ptr [ebp+8]
      74603eb3 e887feffff      call    MSVCR90!malloc (74603d3f)
      74603eb8 59              pop     ecx
      74603eb9 85c0            test    eax,eax
      74603ebb 74e6            je      MSVCR90!operator new+0xa (74603ea3)
      74603ebd c9              leave

      我們可以看到operator new最終調用的是malloc, 如果再深入下去, 會發現malloc調用的是Kernel32!HeapAlloc, 而HeapAlloc調用的又是ntdll!RtlAllocateHeap, 關于heap的布局和分配算法,可以看張銀奎的 軟件調試

      上面論證了new操作符背后的行為: 
      首先調用operator new分配空間, 我們可以重載operator new, 定義自己的內存分配算法
      然后在分配的空間上調用構造函數創建對象, 構造函數內部可能會賦值虛表指針。

      接下來我們看下delete背后的行為。
      我們看到delete調用的是虛表里的第二個函數, 我們先看虛表內容:
      0:000> dps 00aa2154
      00aa2154  00aa1010 NewTest!B::print [f:\test\newtest\newtest\newtest.cpp @ 26]
      00aa2158  00aa1060 NewTest!B::`scalar deleting destructor'
      00aa215c  00000000
      00aa2160  00000048
      00aa2164  00000000

      上面看到虛表里有2個函數, 一個是print, 還有一個是destructor, 我們看下第二個函數的內容:
      0:000> u 00aa1060  L10
      NewTest!B::`scalar deleting destructor':
      00aa1060 56              push    esi
      00aa1061 8bf1            mov     esi,ecx
      00aa1063 c7064821aa00    mov     dword ptr [esi],offset NewTest!A::`vftable' (00aa2148)
      00aa1069 a15820aa00      mov     eax,dword ptr [NewTest!_imp_?coutstd (00aa2058)]
      00aa106e 50              push    eax
      00aa106f e84c010000      call    NewTest!std::operator<<<std::char_traits<char> > (00aa11c0)
      00aa1074 83c404          add     esp,4
      00aa1077 f644240801      test    byte ptr [esp+8],1
      00aa107c 7409            je      NewTest!B::`scalar deleting destructor'+0x27 (00aa1087)
      00aa107e 56              push    esi
      00aa107f e806030000      call    NewTest!operator delete (00aa138a)
      00aa1084 83c404          add     esp,4
      00aa1087 8bc6            mov     eax,esi
      00aa1089 5e              pop     esi
      00aa108a c20400          ret     4

      我們可以看到虛表里放的是 B 的 scalar deleting destructor , 它里面包含兩部分代碼, 一個是我們真正定義的析構函數的代碼,還有一部分就是operator delete ( operator delete又會去調用free, free調用kernel32!HeapFree)。這里的 scalar deleting destructor顯然不是B的析構函數~B(), 這是編譯器幫我產生的一個函數,它就是給delete B類型對象用的。 

      接下來我們看看對于數組類型的指針, C++編譯器背后是如何處理的, 把代碼改成如下:
      int _tmain(int argc, _TCHAR* argv[])
      {
      A* p = new A[10];
      delete []p;

      return 0;
      }

      下面是生成的匯編代碼:
      NewTest!wmain:
      01181030 6a2c            push    2Ch 
      01181032 e8c4030000      call    NewTest!operator new[] (011813fb) //通過operator new分配44自己
      01181037 83c404          add     esp,4
      0118103a 85c0            test    eax,eax
      0118103c 7444            je      NewTest!wmain+0x52 (01181082)
      0118103e 56              push    esi
      0118103f 6810101801      push    offset NewTest!A::~A (01181010) //A的析構函數
      01181044 6800111801      push    offset NewTest!A::A (01181100)  //A的構造函數
      01181049 6a0a            push    0Ah //10
      0118104b 8d7004          lea     esi,[eax+4] //跨過了頭四個字節
      0118104e 6a04            push    4    //對象大小
      01181050 56              push    esi //esi里放的是對象列表的起始地址(跨過了頭四個字節) 
      01181051 c7000a000000    mov     dword ptr [eax],0Ah //頭四個字節寫入對象列表數量(10)
      01181057 e812040000      call    NewTest!`eh vector constructor iterator' (0118146e)
      0118105c 85f6            test    esi,esi
      0118105e 7421            je      NewTest!wmain+0x51 (01181081)
      01181060 837efc00        cmp     dword ptr [esi-4],0 //判斷對象數量是否 為 0
      01181064 8d46fc          lea     eax,[esi-4] //包含對象數量的地址保存到  eax
      01181067 740f            je      NewTest!wmain+0x48 (01181078)
      01181069 8b06            mov     eax,dword ptr [esi] //取A的虛表地址
      0118106b 8b5004          mov     edx,dword ptr [eax+4] //虛表里的第二個函數
      0118106e 6a03            push    3
      01181070 8bce            mov     ecx,esi
      01181072 ffd2            call    edx
      01181074 5e              pop     esi
      01181075 33c0            xor     eax,eax
      01181077 c3              ret

      重點看上面紅色的代碼, 我們可以看到, 在new一個數組時,編譯器幫我們做了下面一些事情:
      (1)調用數組的operator new[] 分配內存, 大小為 4 + sizeof(object) * count, 其中頭四個字節為對象數量
      (2)調用NewTest!`eh vector constructor iterator(pArrayAddress, sizeof(object),  object_count, pFunConstructor, pFunDestructor), 
      其中 pFunDestructor為析構函數, pFunConstructor為構造函數, object_count為對象數量, sizeof(object)為對象大小,pArrayAddress為起始地址。, 

      下面我們反匯編 NewTest!`eh vector constructor iterator: 
      0:000> u 0118146e L50
      NewTest!`eh vector constructor iterator':
      0118146e 6a10            push    10h
      01181470 6890221801      push    offset NewTest!__rtc_tzz+0x8 (01182290)
      01181475 e8d2040000      call    NewTest!__SEH_prolog4 (0118194c)
      0118147a 33c0            xor     eax,eax
      0118147c 8945e0          mov     dword ptr [ebp-20h],eax
      0118147f 8945fc          mov     dword ptr [ebp-4],eax
      01181482 8945e4          mov     dword ptr [ebp-1Ch],eax
      01181485 8b45e4          mov     eax,dword ptr [ebp-1Ch] //臨時計數,初始為0
      01181488 3b4510          cmp     eax,dword ptr [ebp+10h]  //將臨時計數和對象數量比較
      0118148b 7d13            jge     NewTest!`eh vector constructor iterator'+0x32 (011814a0) //如果臨時計數大于對象數量則退出循環
      0118148d 8b7508          mov     esi,dword ptr [ebp+8] //保存第一個參數(起始地址)到 esi
      01181490 8bce            mov     ecx,esi //賦this指針到ecx
      01181492 ff5514          call    dword ptr [ebp+14h] //調用構造函數
      01181495 03750c          add     esi,dword ptr [ebp+0Ch] //移動指針, 加上對象大小
      01181498 897508          mov     dword ptr [ebp+8],esi //保存新對象地址到第一個參數
      0118149b ff45e4          inc     dword ptr [ebp-1Ch] //增加臨時計數
      0118149e ebe5            jmp     NewTest!`eh vector constructor iterator'+0x17 (01181485)
      011814a0 c745e001000000  mov     dword ptr [ebp-20h],1
      011814a7 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
      011814ae e808000000      call    NewTest!`eh vector constructor iterator'+0x4d (011814bb)
      011814b3 e8d9040000      call    NewTest!__SEH_epilog4 (01181991)
      011814b8 c21400          ret     14h


      我們可以看到NewTest!`eh vector constructor iterator是編譯器幫我們生成的函數, 它的作用就是為數組中的每個對象都調用構造函數。

      接下我們再看看數組形式的delete []在背后究竟干了什么?
      重點看上面紫色的代碼:
      NewTest!wmain:
      ....
      01181060 837efc00        cmp     dword ptr [esi-4],0 //判斷對象數量是否 為 0
      01181064 8d46fc          lea     eax,[esi-4] //包含對象數量的地址保存到  eax
      01181067 740f            je      NewTest!wmain+0x48 (01181078)
      01181069 8b06            mov     eax,dword ptr [esi] //取A的虛表地址
      0118106b 8b5004          mov     edx,dword ptr [eax+4] //虛表里的第二個函數
      0118106e 6a03            push    3
      01181070 8bce            mov     ecx,esi
      01181072 ffd2            call    edx
      ....
       可以看到它將對象列表起始地址保存到ecx, 然后調用對象虛表里的第二個函數, 并且傳入參數是3, 我們先看對象虛表內容:
      0:000> dps 01182148 
      01182148  01181000 NewTest!A::print [f:\test\newtest\newtest\newtest.cpp @ 11]
      0118214c  01181090 NewTest!A::`vector deleting destructor'

      我們看看該函數究竟干了什么:
      0:000> u 01181090  L40
      NewTest!A::`vector deleting destructor':
      01181090 53              push    ebx
      01181091 8a5c2408        mov     bl,byte ptr [esp+8]
      01181095 56              push    esi
      01181096 8bf1            mov     esi,ecx
      01181098 f6c302          test    bl,2 //是否需要調用析構函數
      0118109b 742b            je      NewTest!A::`vector deleting destructor'+0x38 (011810c8)
      0118109d 8b46fc          mov     eax,dword ptr [esi-4]
      011810a0 57              push    edi
      011810a1 6810101801      push    offset NewTest!A::~A (01181010)
      011810a6 8d7efc          lea     edi,[esi-4]
      011810a9 50              push    eax
      011810aa 6a04            push    4
      011810ac 56              push    esi
      011810ad e87f040000      call    NewTest!`eh vector destructor iterator' (01181531)
      011810b2 f6c301          test    bl,1 //是否需要釋放內存
      011810b5 7409            je      NewTest!A::`vector deleting destructor'+0x30 (011810c0)
      011810b7 57              push    edi
      011810b8 e85f030000      call    NewTest!operator delete[] (0118141c)
      011810bd 83c404          add     esp,4
      011810c0 8bc7            mov     eax,edi
      011810c2 5f              pop     edi
      011810c3 5e              pop     esi
      011810c4 5b              pop     ebx
      011810c5 c20400          ret     4

      可以看到它內部調用的是NewTest!`eh vector destructor iterator, 而如果再跟蹤NewTest!`eh vector destructor iterator,
      會看所有數組里的對象調用析構函數, 最后調用operator delete[]釋放所有內存。

      我們可以看到數組new[]和delete[]的關鍵是, C++編譯器在數組起始地址之前的4個字節保存了對象的數量N,后面會根據這個數量值進行N次的構造和析構 。 

      最后申明下, 上面的分析僅限于VS2008, 實際上在符合C++標準的前提下, 各個C++編譯器有各自不同的實現。

      我們可以看到C++ 編譯器在背后干了很多事情,可能會內聯我們的函數, 也可以修改和產生其他一些函數, 而這是很多C開發者受不了的事情, 所以在內核級別, 很多人寧愿用C來減少編譯器背后的干擾。

      最后思考一下, 如果我們代碼這樣寫,會怎么樣? 
      int _tmain(int argc, _TCHAR* argv[])
      {
      A* p = new B[10];
      delete []p;

      return 0;
      }
      答案請看 這里 
      posted on 2013-11-17 21:25  Richard Wei  閱讀(707)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 亚洲韩国精品无码一区二区三区 | 这里只有精品在线播放| 亚洲精品乱码久久久久久蜜桃图片| 九九综合va免费看| 97久久超碰国产精品2021| 国内熟女中文字幕第一页| 国产天堂亚洲国产碰碰| 内射无套内射国产精品视频| 国产精品中文字幕免费| 欧美性色黄大片www喷水| 亚洲天堂av日韩精品| 精品国产成人国产在线观看| 日韩毛片在线视频x| 久草热久草热线频97精品| 四虎永久在线高清免费看| 曰韩无码二三区中文字幕| 久久精品国产久精国产| 国产精品免费看久久久| 丝袜人妖av在线一区二区 | 亚洲av一本二本三本| 日韩精品一卡二卡在线观看| 国产欧美VA天堂在线观看视频 | 亚洲中文字幕精品无人区| 宝贝腿开大点我添添公视频免| 在线无码中文字幕一区| 亚洲性人人天天夜夜摸18禁止| 国产精品日日摸夜夜添夜夜添2021| 敦煌市| 98久久人妻少妇激情啪啪| 高清日韩一区二区三区视频| 亚洲+成人+国产| 柳林县| 久久人人妻人人爽人人爽| 国产精品亚洲电影久久成人影院| 亚洲一区av在线观看| 亚洲男人第一无码av网站| 中文字幕有码无码AV| 国产不卡一区二区在线| 国产人妻精品午夜福利免费 | 国产精品日韩中文字幕熟女| 色欲久久久天天天综合网精品|