6. c 函式呼叫機制概述
在 linux 核心程式 boot/head.s 執行完基本初始化操作之後,就會跳轉區執行 init/main.c 程式。那麼 head.s 程式時如何把執行控制轉交給 init/mian.c 程式呢?即匯程式設計序時如何呼叫執行 c 語言程式的?這裡我們首先描述一下 c 函式的呼叫機制、控制器傳遞方式,然後說明 head.s 程式跳轉到 c 程式的方法。
函式呼叫操作包括從一塊**到另一塊**之間的雙向資料傳遞和執行控制轉移。資料傳遞通過函式引數和返回值來進行。另外,我們還需要在進入函式時未函式的區域性變數分配儲存空間,並且在退出時收回這部分空間。intel 80x86 cpu 為控制傳遞提供了簡單的指令,而資料的傳遞和區域性變數儲存空間的分配與**則通過棧操作來實現。
7. 棧幀結構和控制轉移權方式
大多數 cpu 上的程式實現使用棧來支援函式呼叫操作。棧被用來傳遞引數、儲存返回資訊、臨時儲存暫存器原有值以備恢復以及用來儲存區域性資料。單個函式呼叫操作所使用的棧部分被被稱為棧幀(stack frame)結構,其通常結構如下。棧幀結構的兩端由兩個指標來指定。暫存器 ebp 通常用作幀指標(frame pointer),而 esp 則用作棧指標(stack pointer)。在函式執行過程中,棧指標 esp 會隨著資料的入棧和出棧而移動,因此函式中對大部分資料的訪問都基於幀指標 ebp 進行。
對於函式 a 呼叫函式 b 的情況,傳遞給 b 的引數包含在 a 的棧幀中。當 a 呼叫 b 時,函式 a 的返回位址(呼叫返回後繼續執行的指令位址)被壓入棧中,棧中該位置也明確指明了 a 棧幀的結束處。而 b 的棧幀則從隨後的棧部分開始,即圖中儲存棧指標(ebp)的地方開始。再隨後則用於存放任何儲存的暫存器值以及函式的臨時值。
b 函式通亞航也使用棧來儲存不能放在暫存器中的區域性變數值。例如由於通常 cpu 的暫存器數量有限而不能夠存放函式的所有區域性資料,後者有些區域性變數是陣列或 結構,因此必須使用陣列或結構引用來訪問。還有就是 c 語言的位址操作符 『&』 被應用到乙個區域性變數上時,我們就需要為該變數生成乙個位址,即為變數的位址指標分配以空間。最後,b 函式會使用棧來儲存呼叫任何其它函式的引數。
棧是往低(小)位址方向擴充套件的,而 esp 指向當前棧頂處的元素。通過使用 push 和 pop 指令我們可以把資料壓入棧中或從棧中彈出。對於沒有指定初始值的資料所需要的儲存空間,我們可以通過把棧指標遞減適當的值來做到。類似地,通過增加棧指標值我們可以**棧中已分配的空間。
指令 call 和 ret 用於處理函式呼叫和返回操作。呼叫指令 call 的作用是把返回位址壓入棧中並且跳轉到被呼叫函式開始處執行。返回位址是程式中緊隨呼叫指令 call 後面一條指令的位址。因此當被調函式返回時就會從該位置繼續執行。返回指令 ret 用於彈出棧頂處的位址並跳轉到該位址處。在使用該指令之前,應該先正確處理棧中內容,使得當前棧指標所指位置內容正是先前 call 指令儲存得返回位址。另外,若返回值睡個整數或乙個指標,那麼暫存器 eax 將被預設用來傳遞返回值。
儘管某一時刻只有乙個函式在執行,但我們還是需要確定乙個函式(呼叫者)呼叫其他函式(被呼叫者)時,被呼叫者不會修改會覆蓋呼叫者今後要用到的暫存器內容。因此 intel cpu 採用了所有函式必須遵守的暫存器用法統一慣例。該慣例指明,暫存器 eax、edx 和 ecx 的內容必須有呼叫者自己負責儲存。當函式 b 被 a 呼叫時,函式 b 可以在不用儲存這些暫存器內容的情況下任意使用它們而不會毀壞函式 a 所需要的任何資料。另外,暫存器 ebx、esi 和 edi 的內容則必須有被呼叫者 b 來保護。當被呼叫者需要使用這些暫存器中的任意乙個時,必須首先在棧中儲存其內容,並在退出時恢復這些就餐器的內容。因為呼叫者 a (或者一些更高層的函式)並不負責儲存這些暫存器內容,但可能在以後的操作中還需要用到原先的值。還有暫存器 ebp 和 esp 也必須遵守第二個慣例用法。
8. 函式呼叫舉例
作為乙個例子,我們來觀察下面 c 程式 exch.c 中函式呼叫的處理過程。
[root@rockman 0710]# cat exch.c
#include void swap(int* a, int* b)
int main()
其中函式 swap() 用於交換兩個變數的值。c 程式中的主程式 main() 也是乙個函式,它在呼叫了 swap() 之後返回交換後的結果。這兩個函式的棧幀結構如下圖所示。
可以看出,函式 swap() 從呼叫者(mian())的棧幀中獲取其引數。圖中的位置資訊相對於暫存器 ebp 中的幀指標。棧幀左邊的數字指出了相對於幀指標的位址偏移值。在像 gdb 這樣的偵錯程式中,這些數值都用 2 的補碼表示。例如 『-4』 被表示成 『0xfffffffc』,『-12』 會被表示成 『0xfffffff4』。
呼叫者 main() 的棧幀結構中包括區域性變數 a 和 b 的儲存空間,相對於幀指標位於 -4 和 -8 偏移處。由於我們需要為這兩個區域性變數生成位址。因此他們必須儲存在棧中而非常簡單地存放在暫存器中。
GNU C語言的 擴充套件(六)內建函式
gnu c 提供了大量的內建函式,其中很多是標準 c 庫的內建版本,例如 memcpy 它們與對應的 c 庫函式功能相同。而其他內建的名字通常以 builtin 開始。內建函式 builtin return address 返回當前函式或其呼叫者的返回位址,引數 level 指定在棧上搜尋框架的個數...
c語言呼叫cpp函式 C語言 函式的遞迴呼叫
乙個函式在呼叫的過程 現直接或者間接呼叫該函式本身的情況,稱為遞迴呼叫,這種函式稱為遞迴函式 在寫遞迴函式時,需要解決如下兩個問題 1 遞迴的出口條件 2 遞迴公式 遞迴函式的 一般為 if 遞迴出口條件 返回符合出口條件的函式值或輸出結果 else 遞迴公式 雖然演算法一致,但n不同,y不同,在記...
10 「撿現成」之函式呼叫
1.函式是一種基本規律的抽象,可重用。例如說圓面積 正方形邊長,橢圓面積等。這些被發現的公式或定理或規律被固化下來,我們在遇到的時候可以直接套用這些定義好的函式。2.python內建了很多的函式,這些函式為我們省去了大量的工作 3.我們可以 撿現成 直接呼叫定義好的方法,而不必事必躬親自己去實現 4...