DSP編程技巧之20---理解函數(shù)的調(diào)用過(guò)程
在我們使用C/C++對(duì)DSP進(jìn)行編程的時(shí)候,函數(shù)無(wú)疑是功能模塊劃分的重要組成部分,這些函數(shù)之間則通過(guò)顯式地調(diào)用或者中斷等方式來(lái)共同工作。除了對(duì)特定的RTS庫(kù)中的函數(shù)(例如某些數(shù)學(xué)函數(shù))的調(diào)用按照它們內(nèi)置規(guī)則進(jìn)行分配外,我們自定義的函數(shù)之間的調(diào)用則需要遵循一定的規(guī)則,了解這一過(guò)程對(duì)理解程序的執(zhí)行和調(diào)試也是十分有幫助的,下面我們就來(lái)解讀一下函數(shù)的調(diào)用過(guò)程,并且可以從其中了解到CPU寄存器、FPU寄存器以及棧(stack)在這一過(guò)程中的作用。
本文引用地址:http://m.butianyuan.cn/article/262925.htm一.父函數(shù)調(diào)用子函數(shù)
在父函數(shù)調(diào)用子函數(shù)(被調(diào)函數(shù))時(shí),通常會(huì)執(zhí)行以下的步驟:
1.如果寄存器不是SOE類型的(入口保存,save on entry),即它的值沒(méi)有被被調(diào)用函數(shù)占用,但是在被調(diào)用函數(shù)返回值之后又會(huì)用到該寄存器的值的話,則該寄存器的值被保存在棧中。
2.如果被調(diào)函數(shù)返回一個(gè)結(jié)構(gòu)體,則調(diào)用函數(shù)會(huì)為結(jié)構(gòu)體分配空間,并且把這段空間的地址作為第一個(gè)參數(shù)傳遞給被調(diào)函數(shù),被調(diào)函數(shù)需要?jiǎng)?chuàng)建一個(gè)該結(jié)構(gòu)體的本地副本。
3.傳遞給被調(diào)函數(shù)的參數(shù)一般情況下會(huì)保存在寄存器中,在必要的情況下則會(huì)保存在棧中,因?yàn)榧拇嫫鞯臄?shù)量有限;具體的規(guī)則是:
(1)如果目標(biāo)器件是FPU,并且傳遞的有32位的浮點(diǎn)從那時(shí),則前4個(gè)浮點(diǎn)參數(shù)被保存在R0H-R3H這4個(gè)FPU寄存器中(注意與CPU寄存器AR0H-AR3H相區(qū)別)。
(2)如果有64位的整形(longlong)參數(shù),則第一個(gè)64位整形參數(shù)的高32位存入ACC寄存器中,低32位存入P寄存器中,其它的64位整形參數(shù)按照逆序(函數(shù)聲明中參數(shù)列表里最左邊的參數(shù)最后被壓入棧中)保存在棧中。
此外,如果P寄存器被用于參數(shù)傳遞,則對(duì)該函數(shù)的裝入(prolog)和排空(epilog)的提取的優(yōu)化功能(通過(guò)減小性能達(dá)到減小程序尺寸)被禁止。
(3)如果參數(shù)中有任何的32位長(zhǎng)整形或者浮點(diǎn)型,則第一個(gè)會(huì)放入ACC寄存器中,其它的32位參數(shù)則按照逆序保存在棧中。
(4)指針參數(shù)被放入CPU寄存器XAR4和XAR5中,其它的指針則存入棧中。
(5)剩余的16位的參數(shù)在CPU寄存器AL,AH,XAR4和XAR5可用的情況下,按照這一寄存器的順序被保存在它們中。
4.任何沒(méi)有被存入寄存器的參數(shù)都會(huì)被以逆序壓入棧中,所有的32位參數(shù)在壓入棧中時(shí)都會(huì)對(duì)齊到偶數(shù)地址。
如果一個(gè)函數(shù)的參數(shù)中使用了省略號(hào),即參數(shù)個(gè)數(shù)是可變的,則最后一個(gè)顯式聲明的參數(shù)在壓入棧中之后,它在棧中的地址可以用來(lái)定位未顯式聲明的參數(shù)。
5.棧指針SP必須在父函數(shù)調(diào)用子函數(shù)之前偶對(duì)齊。如果不是偶對(duì)齊,則需要把SP加1.
6.父函數(shù)使用LCR指令(使用返回程序指針寄存器RPC的方式來(lái)進(jìn)行22位的長(zhǎng)調(diào)用)來(lái)調(diào)用子函數(shù),在調(diào)用時(shí)RPC寄存器的值會(huì)被壓入棧中,從而可以把返回地址保存在RPC寄存器中。
7.最后,棧被對(duì)齊到函數(shù)的邊界上。
二.子函數(shù)響應(yīng)父函數(shù)
在子函數(shù)被調(diào)用時(shí),通常會(huì)執(zhí)行以下的步驟:
1.如果被調(diào)函數(shù)修改了XAR1、XAR2或者XAR3的值,則必須保存它們的值,因?yàn)樵谡{(diào)用前后,父函數(shù)假設(shè)這3個(gè)寄存器的值在被返回之前是被保留的。如果目標(biāo)是FPU,并且在被調(diào)函數(shù)中修改了R4H-R8H的值,則同樣需要保存它們的值。
2.被調(diào)用的函數(shù)需要在棧中為所有的本地變量、臨時(shí)存儲(chǔ)區(qū)域已經(jīng)任何被調(diào)用的參數(shù)分配足夠的空間。在通過(guò)為SP寄存器加偏移量跳轉(zhuǎn)到被調(diào)函數(shù)之后,這段存儲(chǔ)空間就立刻被分配了。
3.棧被對(duì)齊到函數(shù)的邊界上。
4.如果被調(diào)用的函數(shù)參數(shù)中有結(jié)構(gòu)體,則它實(shí)際接收到的是該結(jié)構(gòu)體的指針。如果在被調(diào)函數(shù)中對(duì)該結(jié)構(gòu)體進(jìn)行了寫操作,則必須在棧中分配空間以創(chuàng)建該結(jié)構(gòu)體的副本,在完成操作之后把本地結(jié)構(gòu)體通過(guò)指針復(fù)制回原有的結(jié)構(gòu)體。如果在被調(diào)函數(shù)中不對(duì)傳入的結(jié)構(gòu)體參數(shù)進(jìn)行寫操作,則可以通過(guò)對(duì)其指針的操作來(lái)完成參數(shù)的引用。
5.完成參數(shù)傳入之后,被調(diào)函數(shù)執(zhí)行它本身的代碼。
6.功能執(zhí)行完成之后,被調(diào)函數(shù)返回值,根據(jù)返回值的類型,它們值的保存位置分別為:
16位整數(shù):AL寄存器
32位整數(shù):ACC寄存器
64位整數(shù):ACC和P寄存器
16位或者22位指針:XAR4寄存器
FPU下的32位浮點(diǎn)數(shù):R0H寄存器
結(jié)構(gòu)體:其指針保存在XAR4寄存器中
在返回結(jié)構(gòu)體的情況下,例如s=f(x),其中s為結(jié)構(gòu)體,f為函數(shù),則可以直接用f(&s,x)的方式在父函數(shù)中調(diào)用子函數(shù)f,通過(guò)結(jié)構(gòu)體指針,被調(diào)函數(shù)可以自動(dòng)返回結(jié)構(gòu)體的值了。
7.通過(guò)把SP中減去調(diào)用子函數(shù)時(shí)加的偏移量,SP寄存器可以重新指向父函數(shù)。
8.被調(diào)函數(shù)恢復(fù)所有在第一步中保存的建城七隊(duì)值。
9.被調(diào)函數(shù)使用LRETR(使用PC指針?lè)祷?指令返回,PC寄存器的值被置為RPC寄存器中的值,即返回地址,然后RPC寄存器中的原有值被推出棧并重新保存在RPC寄存器中。
通過(guò)以上的描述,可以看出棧在函數(shù)調(diào)用前后起著非常關(guān)鍵的中繼作用。所以,如果在調(diào)用時(shí)傳遞的參數(shù)非常多,例如傳遞了一個(gè)很長(zhǎng)的數(shù)組,或者有多個(gè)64位的參數(shù),則棧很有可能沒(méi)有足夠的空間來(lái)完成參數(shù)的暫存,造成棧的溢出,甚至造成程序運(yùn)行結(jié)果的異?;蛘咤e(cuò)誤的輸出結(jié)果,因?yàn)榫幾g器無(wú)法檢查棧的溢出錯(cuò)誤(除非我們自己編寫某些代碼來(lái)檢測(cè)),所以要為棧分配一個(gè)相對(duì)較大的存儲(chǔ)空間,它的默認(rèn)值是1K字。即使是非常小的程序,常用例程里棧的長(zhǎng)度也往往能達(dá)到0x400這樣的長(zhǎng)度。
c++相關(guān)文章:c++教程
評(píng)論