資料介紹
14.9 函數調用
函數設計的基本原則是使其函數體盡量的小。這樣編譯器可以對函數做更多的優化。
14.9.1 減少函數調用開銷
ARM上的函數調用開銷比非RISC體系結構上的調用開銷?。?br /> · 調用返回指令“BL”或“MOV pc,lr”一般只需要6個指令周期(ARM7上)。
· 在函數的入口和出口使用多寄存器加載/存儲指令LDM和STM(Thumb指令使用PUSH和POP)提高函數體的執行效率。
ARM體系結構過程調用標準AAPCS定義了如何通過寄存器傳遞參數和返回值。函數中的前4個整型參數是通過ARM的前4個寄存器r0、r1、r2和r3來傳遞的。傳遞參數可以是與整型兼容的數據類型,如字符類型char、半字類型short等。
注意如果是雙字類型,如long long型,只能通過寄存器傳遞兩個參數。
不能通過寄存器傳遞的參數,通過函數堆棧來傳遞。這樣不論是函數的調用者還是被調用者都必須通過訪問堆棧來訪問參數,使程序的執行效率下降。
下面的例子顯示了函數調用是傳遞4個參數和多于4個參數的區別。
傳遞4個參數的函數調用源文件如下。
int func1(int a, int b, int c, int d)
{
return a+b+c+d;
}
int caller1(void)
{
return func1(1,2,3,4);
}
編譯的結果如下。
func1
ADD r0,r0,r1
ADD r0,r0,r2
ADD r0,r0,r3
MOV pc,lr
caller1
MOV r3,#4
MOV r2,#3
MOV r1,#2
MOV r0,#1
B func1
如果程序需要傳遞6個參數,變為如下形式。
int func2(int a, int b, int c, int d,int e,int f)
{
return a+b+c+d+e+f;
}
int caller2(void)
{
return func1(1,2,3,4,5,6);
}
則編譯后的匯編文件如下。
func2
STR lr, [sp,#-4]!
ADD r0,r0,r1
ADD r0,r0,r2
ADD r0,r0,r3
LDMIB sp,{r12,r14}
ADD r0,r0,r12
ADD r0,r0,r14
LDR pc,{sp},#4
caller2
STMFD sp!,{r2,r3,lr}
MOV r3,#6
MOV r2,#5
STMIA sp,{r2,r3}
MOV r3,#4
MOV r2,#3
MOV r1,#2
MOV r0,#1
BL func2
LDMFD sp!,{r2,r3,pc}
綜上所述,為了在程序中高效的調用函數,最好遵循以下規則。
· 盡量限制函數的參數,不要超過4個,這樣函數調用的效率會更高。
· 當傳遞的參數超過4個時,要將多個相關參數組織在一個結構體中,用傳遞結構體指針來代替多個參數。
· 避免將傳遞的參數定義為long long型,因為傳遞一個long long型的數據將會占用兩個32位寄存器。
· 函數中存在浮點運算時,避免使用double型參數。
14.9.2 使用__value_in_regs返回結構體
編譯選項__value_in_regs指示編譯器在整數寄存器中返回4個整數字的結構或者在浮點寄存器中返回4個浮點型或雙精度型值,而不使用存儲器。
下面的例子顯示了__value_in_regs選項的用法。
typedef struct { int hi; uint lo; } int64; // 注意該結構中,高位為有符號整數,低位為無符號整數
__value_in_regs int64 add64(int64 x, int64 y)
{ int64 res;
res.lo = x.lo + y.lo;
res.hi = x.hi + y.hi;
if (res.lo 《 y.lo) res.hi++; // carry from low word
return res;
}
void test(void)
{ int64 a, b, c, sum;
a.hi = 0x00000000; a.lo = 0xF0000000;
b.hi = 0x00000001; b.lo = 0x10000001;
sum = add64(a, b);
c.hi = 0x00000002; c.lo = 0xFFFFFFFF;
sum = add64(sum, c);
}
編譯后的結果如下所示。
add64
ADDS a2,a2,a4
ADC a1,a3,a1
MOV pc,lr
test
STMDB sp!,{lr}
MOV a1,#0
MOV a2,#&f0000000
MOV a3,#1
MOV a4,#&10000001
BL add64
MOV a3,#2
MVN a4,#0
LDMIA sp!,{lr}
B add64
當使用__value_in_regs定義結構體時,編譯的代碼大小為52字節,如果不使用__value_in_regs選項,則編譯出的結果為160字節(本書中沒有列出未使用__value_in_regs時的編譯結果,讀者有興趣可以自己上機試驗)。
14.9.3 葉子函數
所謂葉子函數(leaf function)就是在其函數體內不存在對其他函數調用,它也常被稱為終級函數。因為葉子函數不需要調用其他函數,所有沒有保存/恢復寄存器的操作,因此執行效率比一般函數要高。
當函數中必須對一些寄存器進行保存時,可以使用高效率的多寄存器存儲指令STM,對需要保存的寄存器內存一次性存儲。
正是由于葉子函數執行的高效性,所以在編程時,盡量將子程序編寫為葉子函數,這樣即使程序中多次調用也不會影響代碼性能。
為了高效的調用函數,可以遵循下面函數調用原則。
· 避免在被頻繁調用的函數中調用其他函數,以保證被頻繁調用的函數被編譯器編譯為葉子函數。
· 把比較小的被調用函數和調用函數放在同一個源文件中,并且要先定義后調用,編譯器就可以優化函數調用或內聯較小的函數。
· 對性能影響較大的重要函數可使用關鍵字_inline進行內聯。
14.9.4 嵌套優化
注意嵌套優化(Tail-Call optimization)只適用于armcc。編譯時如果使用-g或-debug選項,編譯器自動關閉該功能。
一個函數如果在其結束時調用了另一個函數,則編譯器使用B指令調轉到被調用函數,而非BL指令。這樣就避免了一級不必要的函數返回。圖14.3顯示了嵌套優化的調用過程。

圖14.3 嵌套優化函數調用過程
當編譯時使用-O1或-O2選項時,編譯器都執行這種嵌套優化。需要注意的是,當函數中引用了局部變量地址,由于指針別名問題的影響,即使函數在返回時調用了其他函數,編譯器也不會使用嵌套優化。
下面通過一個例子來分析嵌套優化是如何提高代碼執行效率的。
extern int func2(int);
int func1 (int a, int b)
{ if (a 》 b)
return (func2(a - b));
else
return (func2(b - a));
}
編譯后的代碼如下所示。
func1
CMP a1,a2
SUBLE a1,a2,a1
SUBGT a1,a1,a2
B func2
首先,func1中使用B指令代替BL指令,不用擔心lr寄存器被破壞,減少了對寄存器壓棧保護操作。另外,程序直接從func2返回到調用func1的函數,減少一次函數返回。如果說正常的指令調用過程為:
BL + BL+ MOV pc,lr + MOV pc,lr
那么經過嵌套優化的函數調用過程就可以表示為:
BL + BL+ MOV pc,lr
這樣,總的開銷將減少25%。
14.9.5 單純子函數
所謂單純子函數(Pure Functions)是指那些函數返回值只和調用參數有關。換句話說,就是如果調用函數的參數相同,那么函數的返回結果也相同。如果程序中存在這樣的函數,可以在函數定義時使用_pure進行聲明,這樣在程序編譯時編譯器會根據函數的調用情況對其進行優化。
下面的例子顯示了當函數用_pure聲明時,編譯器對其所做的優化。
程序源碼文件如下。
int square(int x)
{
return x * x;
}
int f(int n)
{
return square(n) + square(n)
}
編譯后的結果如下。
square
MOV a2,a1
MUL a1,a2,a2
MOV pc,lr
f
STMDB sp!,{lr}
MOV a3,a1
BL square
MOV a4,a1
MOV a1,a3
BL square
ADD a1,a4,a1
LDMIA sp!,{pc}
上面的程序中,square函數為“單純子函數”,當使用_pure聲明該函數時編譯器在調用該函數時,將對程序進行優化。
聲明的方法和編譯后的結果如下所示。
__pure int square(int x)
{
return x * x;
}
f
STMDB sp!,{lr}
BL square
MOV a1,a1,LSL #1
LDMIA sp!,{pc}
從編譯后的代碼中可以看到,用_pure聲明的函數在f函數中只調用了一次。
雖然“單純子函數”可以提高代碼執行效率,但同時也會帶來一些負面影響。比如,在“單純子函數”中,不能直接或間接訪問內存地址。所以在程序中使用“單純子函數”時要特別小心。
另外,還可以使用#pragma聲明“單純子函數”,下面的代碼顯示了它的聲明過程。
#pragma no_side_effects
/* function definition */
#pragma side_effects
函數設計的基本原則是使其函數體盡量的小。這樣編譯器可以對函數做更多的優化。
14.9.1 減少函數調用開銷
ARM上的函數調用開銷比非RISC體系結構上的調用開銷?。?br /> · 調用返回指令“BL”或“MOV pc,lr”一般只需要6個指令周期(ARM7上)。
· 在函數的入口和出口使用多寄存器加載/存儲指令LDM和STM(Thumb指令使用PUSH和POP)提高函數體的執行效率。
ARM體系結構過程調用標準AAPCS定義了如何通過寄存器傳遞參數和返回值。函數中的前4個整型參數是通過ARM的前4個寄存器r0、r1、r2和r3來傳遞的。傳遞參數可以是與整型兼容的數據類型,如字符類型char、半字類型short等。
注意如果是雙字類型,如long long型,只能通過寄存器傳遞兩個參數。
不能通過寄存器傳遞的參數,通過函數堆棧來傳遞。這樣不論是函數的調用者還是被調用者都必須通過訪問堆棧來訪問參數,使程序的執行效率下降。
下面的例子顯示了函數調用是傳遞4個參數和多于4個參數的區別。
傳遞4個參數的函數調用源文件如下。
int func1(int a, int b, int c, int d)
{
return a+b+c+d;
}
int caller1(void)
{
return func1(1,2,3,4);
}
編譯的結果如下。
func1
ADD r0,r0,r1
ADD r0,r0,r2
ADD r0,r0,r3
MOV pc,lr
caller1
MOV r3,#4
MOV r2,#3
MOV r1,#2
MOV r0,#1
B func1
如果程序需要傳遞6個參數,變為如下形式。
int func2(int a, int b, int c, int d,int e,int f)
{
return a+b+c+d+e+f;
}
int caller2(void)
{
return func1(1,2,3,4,5,6);
}
則編譯后的匯編文件如下。
func2
STR lr, [sp,#-4]!
ADD r0,r0,r1
ADD r0,r0,r2
ADD r0,r0,r3
LDMIB sp,{r12,r14}
ADD r0,r0,r12
ADD r0,r0,r14
LDR pc,{sp},#4
caller2
STMFD sp!,{r2,r3,lr}
MOV r3,#6
MOV r2,#5
STMIA sp,{r2,r3}
MOV r3,#4
MOV r2,#3
MOV r1,#2
MOV r0,#1
BL func2
LDMFD sp!,{r2,r3,pc}
綜上所述,為了在程序中高效的調用函數,最好遵循以下規則。
· 盡量限制函數的參數,不要超過4個,這樣函數調用的效率會更高。
· 當傳遞的參數超過4個時,要將多個相關參數組織在一個結構體中,用傳遞結構體指針來代替多個參數。
· 避免將傳遞的參數定義為long long型,因為傳遞一個long long型的數據將會占用兩個32位寄存器。
· 函數中存在浮點運算時,避免使用double型參數。
14.9.2 使用__value_in_regs返回結構體
編譯選項__value_in_regs指示編譯器在整數寄存器中返回4個整數字的結構或者在浮點寄存器中返回4個浮點型或雙精度型值,而不使用存儲器。
下面的例子顯示了__value_in_regs選項的用法。
typedef struct { int hi; uint lo; } int64; // 注意該結構中,高位為有符號整數,低位為無符號整數
__value_in_regs int64 add64(int64 x, int64 y)
{ int64 res;
res.lo = x.lo + y.lo;
res.hi = x.hi + y.hi;
if (res.lo 《 y.lo) res.hi++; // carry from low word
return res;
}
void test(void)
{ int64 a, b, c, sum;
a.hi = 0x00000000; a.lo = 0xF0000000;
b.hi = 0x00000001; b.lo = 0x10000001;
sum = add64(a, b);
c.hi = 0x00000002; c.lo = 0xFFFFFFFF;
sum = add64(sum, c);
}
編譯后的結果如下所示。
add64
ADDS a2,a2,a4
ADC a1,a3,a1
MOV pc,lr
test
STMDB sp!,{lr}
MOV a1,#0
MOV a2,#&f0000000
MOV a3,#1
MOV a4,#&10000001
BL add64
MOV a3,#2
MVN a4,#0
LDMIA sp!,{lr}
B add64
當使用__value_in_regs定義結構體時,編譯的代碼大小為52字節,如果不使用__value_in_regs選項,則編譯出的結果為160字節(本書中沒有列出未使用__value_in_regs時的編譯結果,讀者有興趣可以自己上機試驗)。
14.9.3 葉子函數
所謂葉子函數(leaf function)就是在其函數體內不存在對其他函數調用,它也常被稱為終級函數。因為葉子函數不需要調用其他函數,所有沒有保存/恢復寄存器的操作,因此執行效率比一般函數要高。
當函數中必須對一些寄存器進行保存時,可以使用高效率的多寄存器存儲指令STM,對需要保存的寄存器內存一次性存儲。
正是由于葉子函數執行的高效性,所以在編程時,盡量將子程序編寫為葉子函數,這樣即使程序中多次調用也不會影響代碼性能。
為了高效的調用函數,可以遵循下面函數調用原則。
· 避免在被頻繁調用的函數中調用其他函數,以保證被頻繁調用的函數被編譯器編譯為葉子函數。
· 把比較小的被調用函數和調用函數放在同一個源文件中,并且要先定義后調用,編譯器就可以優化函數調用或內聯較小的函數。
· 對性能影響較大的重要函數可使用關鍵字_inline進行內聯。
14.9.4 嵌套優化
注意嵌套優化(Tail-Call optimization)只適用于armcc。編譯時如果使用-g或-debug選項,編譯器自動關閉該功能。
一個函數如果在其結束時調用了另一個函數,則編譯器使用B指令調轉到被調用函數,而非BL指令。這樣就避免了一級不必要的函數返回。圖14.3顯示了嵌套優化的調用過程。

圖14.3 嵌套優化函數調用過程
當編譯時使用-O1或-O2選項時,編譯器都執行這種嵌套優化。需要注意的是,當函數中引用了局部變量地址,由于指針別名問題的影響,即使函數在返回時調用了其他函數,編譯器也不會使用嵌套優化。
下面通過一個例子來分析嵌套優化是如何提高代碼執行效率的。
extern int func2(int);
int func1 (int a, int b)
{ if (a 》 b)
return (func2(a - b));
else
return (func2(b - a));
}
編譯后的代碼如下所示。
func1
CMP a1,a2
SUBLE a1,a2,a1
SUBGT a1,a1,a2
B func2
首先,func1中使用B指令代替BL指令,不用擔心lr寄存器被破壞,減少了對寄存器壓棧保護操作。另外,程序直接從func2返回到調用func1的函數,減少一次函數返回。如果說正常的指令調用過程為:
BL + BL+ MOV pc,lr + MOV pc,lr
那么經過嵌套優化的函數調用過程就可以表示為:
BL + BL+ MOV pc,lr
這樣,總的開銷將減少25%。
14.9.5 單純子函數
所謂單純子函數(Pure Functions)是指那些函數返回值只和調用參數有關。換句話說,就是如果調用函數的參數相同,那么函數的返回結果也相同。如果程序中存在這樣的函數,可以在函數定義時使用_pure進行聲明,這樣在程序編譯時編譯器會根據函數的調用情況對其進行優化。
下面的例子顯示了當函數用_pure聲明時,編譯器對其所做的優化。
程序源碼文件如下。
int square(int x)
{
return x * x;
}
int f(int n)
{
return square(n) + square(n)
}
編譯后的結果如下。
square
MOV a2,a1
MUL a1,a2,a2
MOV pc,lr
f
STMDB sp!,{lr}
MOV a3,a1
BL square
MOV a4,a1
MOV a1,a3
BL square
ADD a1,a4,a1
LDMIA sp!,{pc}
上面的程序中,square函數為“單純子函數”,當使用_pure聲明該函數時編譯器在調用該函數時,將對程序進行優化。
聲明的方法和編譯后的結果如下所示。
__pure int square(int x)
{
return x * x;
}
f
STMDB sp!,{lr}
BL square
MOV a1,a1,LSL #1
LDMIA sp!,{pc}
從編譯后的代碼中可以看到,用_pure聲明的函數在f函數中只調用了一次。
雖然“單純子函數”可以提高代碼執行效率,但同時也會帶來一些負面影響。比如,在“單純子函數”中,不能直接或間接訪問內存地址。所以在程序中使用“單純子函數”時要特別小心。
另外,還可以使用#pragma聲明“單純子函數”,下面的代碼顯示了它的聲明過程。
#pragma no_side_effects
/* function definition */
#pragma side_effects
下載該資料的人也在下載
下載該資料的人還在閱讀
更多 >
- C代碼與javaScript函數的相互調用問題應該如何解決 17次下載
- C語言教程之函數的詳細資料說明 9次下載
- 如何在中斷C函數中調用C++
- Linux教程之Linux C函數參考教程免費下載 4次下載
- C語言實用教程之函數的詳細資料說明 3次下載
- C語言入門基礎教程之函數的詳細資料說明 6次下載
- C語言程序設計教程之如何進行函數與編譯預處理資料概述 4次下載
- C語言程序設計實用教程之函數詳細介紹和應用 2次下載
- C++語言入門教程之C++語言程序設計函數的詳細資料概述免費下載 23次下載
- c#調用matlab函數 24次下載
- C語言教程之不使用strcpy()函數實現 0次下載
- C#教程之調用Outlook發送郵件 4次下載
- C#教程之調用SMTP發送文本內容 5次下載
- C#教程之調用SMTP發送有附件的郵件 16次下載
- C++教程之函數的遞歸調用
- 子函數多層調用的主要注意事項分析 2.3k次閱讀
- 如何查看及更改函數/函數塊的調用環境 2.2k次閱讀
- 博途的多重背景調用 5.5k次閱讀
- SCL中調用函數的示例 3.6k次閱讀
- 什么是Python的遞歸函數 2.5k次閱讀
- C語言內聯函數 1.8k次閱讀
- C程序流程設計之函數 1.5k次閱讀
- 嵌入式軟件架構設計之函數調用 1.6k次閱讀
- 如何寫要被C調用的匯編函數 2k次閱讀
- C語言使用函數調用在內存中究竟發生了什么? 2k次閱讀
- 帶你了解嵌入式C語言函數調用棧 2.6k次閱讀
- 關于DSP中fft函數調用方法 8.8k次閱讀
- 如何在c51程序中調用匯編函數 4.7k次閱讀
- C語言教程之函數指針變量與指針函數的區別(下篇) 2.2k次閱讀
- pic單片機io口控制教程之c語言編程實現 1.2w次閱讀
下載排行
本周
- 1MDD品牌三極管BC807數據手冊
- 3.00 MB | 次下載 | 免費
- 2MDD品牌三極管BC817數據手冊
- 2.51 MB | 次下載 | 免費
- 3MDD品牌三極管D882數據手冊
- 3.49 MB | 次下載 | 免費
- 4MDD品牌三極管MMBT2222A數據手冊
- 3.26 MB | 次下載 | 免費
- 5MDD品牌三極管MMBTA56數據手冊
- 3.09 MB | 次下載 | 免費
- 6MDD品牌三極管MMBTA92數據手冊
- 2.32 MB | 次下載 | 免費
- 7STM32G474 HRTIME PWM 丟波問題分析與解決
- 1.00 MB | 次下載 | 3 積分
- 8新能源電動汽車高壓線束的銅鋁連接解決方案
- 2.71 MB | 次下載 | 2 積分
本月
- 1愛華AIWA HS-J202維修手冊
- 3.34 MB | 37次下載 | 免費
- 2PC5502負載均流控制電路數據手冊
- 1.63 MB | 23次下載 | 免費
- 3NB-IoT芯片廠商的資料說明
- 0.31 MB | 22次下載 | 1 積分
- 4H110主板CPU PWM芯片ISL95858HRZ-T核心供電電路圖資料
- 0.63 MB | 6次下載 | 1 積分
- 5UWB653Pro USB口測距通信定位模塊規格書
- 838.47 KB | 5次下載 | 免費
- 6技嘉H110主板IT8628E_BX IO電路圖資料
- 2.61 MB | 4次下載 | 1 積分
- 7蘇泊爾DCL6907(即CHK-S007)單芯片電磁爐原理圖資料
- 0.04 MB | 4次下載 | 1 積分
- 8蘇泊爾DCL6909(即CHK-S009)單芯片電磁爐原理圖資料
- 0.08 MB | 2次下載 | 1 積分
總榜
- 1matlab軟件下載入口
- 未知 | 935137次下載 | 10 積分
- 2開源硬件-PMP21529.1-4 開關降壓/升壓雙向直流/直流轉換器 PCB layout 設計
- 1.48MB | 420064次下載 | 10 積分
- 3Altium DXP2002下載入口
- 未知 | 233089次下載 | 10 積分
- 4電路仿真軟件multisim 10.0免費下載
- 340992 | 191439次下載 | 10 積分
- 5十天學會AVR單片機與C語言視頻教程 下載
- 158M | 183353次下載 | 10 積分
- 6labview8.5下載
- 未知 | 81602次下載 | 10 積分
- 7Keil工具MDK-Arm免費下載
- 0.02 MB | 73822次下載 | 10 積分
- 8LabVIEW 8.6下載
- 未知 | 65991次下載 | 10 積分
電子發燒友App





創作
發文章
發帖
提問
發資料
發視頻
上傳資料賺積分
評論