程式員的自我修養: 函數調用與堆棧

來源:互聯網
上載者:User

記憶體布局

linux下一個進程的典型記憶體分布:

____________________ 0xffffffff</p><p>kernel space </p><p>____________________ 0xc0000000</p><p>stack</p><p>(向下拓展)</p><p>____________________</p><p>unused</p><p>____________________</p><p>dynamic libraries</p><p>____________________ 0x40000000</p><p>unused</p><p>____________________</p><p>heap</p><p>(向上拓展)</p><p>____________________</p><p>read/write sections </p><p>(.data, .bss)</p><p>____________________</p><p>readonly sections</p><p>(.init, .rodata, .text)</p><p>____________________ 0x0804800</p><p>reserved</p><p>____________________ 0<br />

 

函數與棧

棧儲存一個函數調用所需要的維護資訊,這常常被稱為堆疊框架 或 活動記錄.

儲存如下內容:

1. 函數的放回地址和參數

2. 臨時變數: 包括函數的非靜態局部變數 以及 比一起自動產生的其他臨時變數.

3. 儲存的上下文: 包括在函數調用前後需要保持不變的寄存器.

一個常見的活動記錄如下:

_____________</p><p>參數</p><p>_____________</p><p>返回地址</p><p>_____________</p><p>old ebp <- ebp</p><p>_____________</p><p>儲存的寄存器</p><p>_____________</p><p>局部變數</p><p>_____________</p><p>其他資料</p><p>_____________ <- esp<br />

 

i386函數體的"標準"開頭是這樣的:

1. push ebp: 把ebp壓入棧中

2. mov ebp, esp: ebp = esp (這時ebp指向棧頂, 而此時棧頂就是old ebp)

3. [可選]sub esp, XXX: 在棧上分配XXX位元組的臨時空間

4. [可選]push XXX: 如有必要,儲存名為XXX的幾個寄存器

 

那麼"標準"結尾是這樣的:

1. [可選]pop XXX: 如有必要, 恢複寄存器

2. mov esp, ebp: 恢複esp同時回收局部變數空間

3. pop: 從棧中恢複儲存的ebp的值.

4. ret: 從棧中取得返回地址, 並跳轉到該位置

 

反組譯碼一個函數:

int foo()

{

    return 123;

}

如下:

004113A0 push ebp step one,儲存ebp,讓ebp指向目前的棧頂<br />004113A1 mov ebp, esp</p><p>004113A3 sub esp, 0C0h step two, 在棧上開闢一塊空間</p><p>004113A9 push ebx step three,儲存ebx,esi,edi寄存器<br />004113AA push esi<br />004113AB push edi</p><p>004113AC lea edi, [ebp-0C0h] step four,加入調試資訊<br />004113B2 mov ecx, 30h<br />004113B7 mov eax, 0CCCCCCCh<br />004113BC rep stos dword ptr es[di]</p><p>004113BE mov eax, 7Bh step five,返回123(在這裡傳回值是通過eax寄存器傳遞的)<br />004113C3 pop edi step six,從棧上恢複寄存器<br />004113C4 pop esi<br />004113C5 pop ebx</p><p>004113C6 mov esp, ebp step seven,恢複進入函數前的esp,ebp<br />004113C8 pop ebp</p><p>004113C9 ret step eight,使用ret指令返回<br />

補充:

函數的調用:

call 0x0EA23h

將ECS或EIP壓棧;

轉移到0x0EA23h。

 

ret函數的返回:

ret

彈棧得到EIP或ECS;

返回EIP所指的指令繼續執行。

 

函數傳回值傳遞方式

#include <stdio.h><br />typedef struct big_thing<br />{<br />char buf[128];<br />}big_thing;</p><p>big_thing return_rest()<br />{<br />big_thing b;<br />b.buf[0] = 0;<br />return b;<br />}<br />int main()<br />{<br />big_thing n = return_rest();<br />return 0;<br />}

用虛擬碼表示如下:

void return_test(void *temp)<br />{<br />big_thing b;<br />b.buf[0] = 0;<br />memcpy(temp, &b, sizeof(big_thing));<br />eax = temp;<br />}</p><p>int main()<br />{<br />big_thing temp;<br />big_thing n;<br />return_test(&temp);<br />memcpy(&n, eax, sizeof(big_thing));<br />}

 

步驟如下:

1. 首先main函數在棧上額外開闢了一片空間, 並將這塊空間的一部分作為傳遞傳回值的臨時對象,temp.

2. 將temp對象的地址作為隱藏參數傳遞給return_test函數

3. return_test函數將資料拷貝給temp對象, 並將temp對象的地址用eax傳出.

4. return_test返回之後, main函數將eax指向的temp對象的內容拷貝給n.

 

當c++對象返回時,

#include <iostream><br />using namespace std;</p><p>struct cpp_obj<br />{<br />cpp_obj()<br />{<br />cout << "ctor/n";<br />}</p><p>cpp_obj(const cpp_obj& c)<br />{<br />cout << "copy ctor/n";<br />}</p><p>cpp_obj& operator=(const cpp_obj& rhs)<br />{<br />cout << "operator=/n";<br />return *this;<br />}</p><p>~cpp_obj()<br />{<br />cout << "dtor/n";<br />}<br />};</p><p>cpp_obj return_test()<br />{<br />cpp_obj b;<br />cout << "before return/n";<br />return b;<br />}</p><p>int main()<br />{<br />cpp_obj n;<br />n = return_test();<br />return 0;<br />}

 

結果:

 

ctor<br />ctor<br />before return<br />copy ctor<br />dtor<br />operator=<br />dtor<br />dtor

#include <iostream><br />using namespace std;</p><p>struct cpp_obj<br />{<br />cpp_obj()<br />{<br />cout << "ctor/n";<br />}</p><p>cpp_obj(const cpp_obj& c)<br />{<br />cout << "copy ctor/n";<br />}</p><p>cpp_obj& operator=(const cpp_obj& rhs)<br />{<br />cout << "operator=/n";<br />return *this;<br />}</p><p>~cpp_obj()<br />{<br />cout << "dtor/n";<br />}<br />};</p><p>cpp_obj return_test()<br />{<br />cout << "before return/n";<br />return cpp_obj();<br />}</p><p>int main()<br />{<br />cpp_obj n;<br />n = return_test();<br />return 0;<br />}

當將函數改成這樣時, C++進行了傳回值最佳化, 直接將物件建構在傳出時使用的臨時對象上, 結果如下:

ctor<br />before return<br />ctor<br />operator=<br />dtor<br />dtor

 

 

在windows中,利用VirtualAlloc取得的虛擬空間,類似於向作業系統"批發"空間.

而用HeapAlloc或者是malloc分配的空間實際上是堆空間的"零售".

這是因為如果記憶體管理由作業系統核心來進行,系統調用時的效能開銷較大.而如果由程式自己管理時,有更高的效率.

堆分配演算法有:

1. 空閑鏈表

2. 頭/主體/空閑 位元影像

3. 對象池

在實際系統中, 是利用多種方法共同分配堆空間的.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.