C函式與彙編函式之間引數及返回值傳遞方法

2021-07-24 10:41:44 字數 4652 閱讀 7241

**:

aapcs

對arm

結構的一些標準做了定義,在這裡我們只重點介紹函式呼叫部分,如圖

8所示,

aapcs

為arm

的r0~r15

暫存器做了定義,明確了它們在函式中的職責:

aapcs關於arm暫存器的定義

函式呼叫時的規則如下:

1. 父函式與子函式間的入口引數依次通過r0~r3這4

個暫存器傳遞

。父函式在呼叫子函式前先將

引數存入

到r0~r3中,若只有乙個引數則使用

r0傳遞,

2個則使用r0和

r1傳遞,依次類推,當超過

4個引數時,其它引數通過棧傳遞

。當子函式執行時,根據自身引數個數自動從r0~r3

或者棧中讀取引數。

2. 子函式通過r0

暫存器將返回值傳遞給父函式

。子函式返回時,將返回值存入r0

,當返回到父函式時,父函式讀取

r0獲得返回值。

3. 發生函式呼叫時,r0~r3

是傳遞引數的暫存器,即使是父函式沒有引數

需要傳遞,子函式也可以任意更改r0~r3

暫存器,無需考慮會破壞它們在父函式中儲存的數值,返回父函式前無需恢復其值。

aapcs

規定,發生函式呼叫

前,由父函式將r0~r3

中有用的資料壓棧,然後才能呼叫子函式,以防止父函式

r0~r3

中的有用

資料被子函式

破壞。4. 

r4~r11為普通的通用暫存器,若子函式需要使用這些暫存器

,則需要將這些暫存器先壓棧

然後再使用

,以免破壞了這些暫存器中儲存的父函式的數值,子函式返回父函式前需要

先出棧恢復其數值,然後再返回父函式。aapcs

規定,發生函式呼叫時,父函式無需對這些暫存器進行壓棧處理,若子函式需要使用這些暫存器,則由子函式負責壓棧,以防止父函式

r4~r11

中的資料被破壞。

5. 編譯器在編譯時就確定了函式間的呼叫關係,它會使函式間的呼叫遵守3、4

條規定。

但編譯器無法預知中斷函式的呼叫,被中斷的函式無法提前對r0~r3

進行壓棧處理,因此需要在中斷函式裡對它所使用的r0

~r11壓棧。對於中斷函式,不遵守第

3條規定,遵守第

5條規定。

6. r12暫存器在某些版本的編譯器下另有它用,使用者程式不能使用,因此我們在編寫彙編函式時也必須

對它進行壓棧處理,確保

它的數值不能被破壞。

7. r13暫存器是

堆疊暫存器(sp

),用來儲存堆疊的當前指標

。8. 

r14暫存器是鏈結暫存器(

lr),用來儲存函式的返回位址。

9. r15暫存器是程式暫存器(

pc),

指向程式當前的位址

。上述只介紹了本手冊中使用到的情形,具體的情況在編寫作業系統**時會涉及到,其它規則請請讀者自行查詢資料。

接下來我們再通過幾個小例子熟悉一下c

函式與彙編函式的呼叫過程。下面的c函式

testfunc1

與彙編函式

testfunc2

的功能是一樣的。

u8 testfunc1(void)

.func testfunc2

testfunc2:

s***b r13!,

@r5,r6,

r10暫存器壓棧

ldr r1, =1

ldr r3, =2

ldr r4, =3

ldr r5, =4

ldr r6, =5

ldr r10, =6

add r0, r1, r3

add r0, r0, r4

add r0, r0, r5

add r0, r0, r6

add r0, r0, r10

ldmia r13!,

@r5,r6,

r10暫存器出棧

.endfunc

testfunc2函式使用了r0、

r1、r3、

r4、r5、

r6、r10共

7個暫存器,遵循

aapcs

規則,在使用

r0、r1和r3

之前並沒有對

它們壓棧,

但對r5、r6

和r10

暫存器進行了壓棧儲存,在函式返回前又出棧還原了這

3個暫存器,這樣

testfunc2

函式返回到它的父函式之後,

r5、r6

和r10

暫存器的數值是沒有改變的,而r0、

r1和r3則分別被改寫為了1、

2和3。

下面我們再來看看c

函式testfunc3

呼叫彙編函式

testfunc4

完成1+2

的運算。

u8 testfunc3(void)

.func testfunc4

testfunc4:

add r0, r0, r1

bx r14;

.endfunc

testfunc3函式在呼叫

testfunc4

函式前已經將引數1和

2分別存入r0和

r1,並將返回位址存入

到r14

中,然後才跳轉到testfunc4

函式,發生函式呼叫。這時程式將執行

testfunc4

函式,它將r0和

r1相加,將結果放入

r0,需要通過

r0將返回值

返回給testfunc3函式。

此時r14

中儲存的就是返回

testfunc3

函式的返回位址,最後

testfunc4

函式跳轉到

r14就返回到了

testfunc3

函式,testfunc3

函式從r0

就可以取出

testfunc4

函式計算的結果了。

下面我們再來看看彙編函式testfunc5呼叫c

函式testfunc6

完成1+2

的運算。

.func testfunc5

testfunc5:

mov r0, #1

mov r1, #2

sub r13, r13, #4

str r14, [r13]

bl testfunc6

ldr r14, [r13]

add r13, r13, #4

bx r14

.endfunc

u8 testfunc6(u8 ucpara1, u8 ucpara2)

testfunc5函式先將引數1和

2存入r0和

r1暫存器

,準備呼叫

testfunc

6函式並傳遞入口引數

,然後將r14

暫存器壓棧,以防止使用

bl指令時存入的

r14返回位址破壞

r14原有的資料,然後呼叫

testfunc6函式。

在呼叫testfunc6

函式時bl

指令會自動將「

ldr r14, [r13]

」這條指令的位址存入

r14,這樣就開始執行

testfunc6

函式了。

testfunc6

函式會自動從r0和

r1暫存器中取出引數,將計算結果存入

r0,通過

r0將返回值返回給

testfunc5

函式。testfunc6

函式跳轉回

testfunc5

函式後,

testfunc5函式從棧中恢復原有的

r14暫存器,完成函式呼叫,此時

r0中的數值就是

testfunc6

函式的計算結果。

當函式比較簡單,不需要壓棧僅使用暫存器便可以完成運算的時候,那麼下面的testfunc7

函式,它的返回值是多少?u8*

testfunc7(

void

)按照上面的分析,

對於這個簡單的函式,編譯器是不會為區域性變數ucpara1

分配記憶體空間的,

ucpara1

只會儲存在暫存器中,因此無從談起它的位址。但這個這麼簡單的函式卻偏偏要獲取這個僅在暫存器中的區域性變數

的位址,遇到這種情況,編譯器在編譯時會特別為ucpara1

專門在棧中分配記憶體,因此也就可以獲取到它的位址了。

當然,這個函式沒有任何意義,僅是舉乙個例子,而且寫c

語言時要避免發生這種情況,因為

testfunc7

函式返回的是棧內區域性變數的位址,當

testfunc7

函式執行完後,

ucpara1

這個區域性變數所在的棧空間已經被釋放,這個棧空間很可能已經被其它變數占用,如果這時候還使用這個位址的話就可能會導致系統崩潰,新手要避免產生這個錯誤。

C函式與彙編函式之間引數及返回值傳遞方法

aapcs對arm結構的一些標準做了定義,在這裡我們只重點介紹函式呼叫部分,如圖8所示,aapcs為arm的r0 r15暫存器做了定義,明確了它們在函式中的職責 圖 8 aapcs關於arm暫存器的定義 一 函式呼叫時的規則如下 1 父函式與子函式間的入口引數依次通過r0 r3這4個暫存器傳遞。父函...

C 與彙編之間相互函式呼叫問題

彙編中可以直接呼叫c函式,c中也可以直接呼叫彙編函式 extern,global宣告即可 但是在c 中不能直接使用,因為c 中的函式與c的函式是不同的,因為c 支援過載,編譯後的函式名會帶有引數的資訊 如 void func int arg 可能會被處理為func int 而c中不需要 所以要想在c...

C 函式的引數傳遞 返回值及函式宣告

函式呼叫發生時,首先要將實參的值按位置傳遞給對應的形參變數。一般情況下,實參和形參的數量和排列順序應該一一對應,並且對應引數的型別必須匹配,而對應引數的引數名則不要求相同。某些特殊情況下也允許引數的數量不對應,這將在函式高階議題中討論。按照引數形式的不同,c 有兩種呼叫方式 傳值呼叫和引用呼叫。舉例...