、简述global关键字的作用_详解static inline关键字
詳解static inline關(guān)鍵字
本文章為知乎用戶 @徐yang喲 原創(chuàng),禁止抄襲!
靈感來源
在查stm32的LL庫部分函數(shù)的API時,有時會查到這種函數(shù):
__STATIC_INLINE void LL_GPIO_SetPinOutputType ( GPIO_TypeDef * GPIOx, uint32_t PinMask, uint32_t OutputType);我不禁對__STATIC_INLINE產(chǎn)生了好奇。在查看源文件后,發(fā)現(xiàn)這個關(guān)鍵字的定義如下
#ifndef __INLINE#define __INLINE __inline #endif #ifndef __STATIC_INLINE#define __STATIC_INLINE static __inline #endif #ifndef __STATIC_FORCEINLINE #define __STATIC_FORCEINLINE static __forceinline #endif很明顯這個關(guān)鍵字是由static 和 __inline 一起構(gòu)成的。所以我們要分別知道這兩個關(guān)鍵字的作用是什么。
關(guān)鍵字定義
static
在C語言中,函數(shù)默認情況下是global的。函數(shù)名前的static關(guān)鍵字使它們變成靜態(tài)。不同于C語言其他的global的函數(shù),訪問static函數(shù)被限制到聲明它們的文件。因此,當(dāng)我們要限制對函數(shù)的訪問時,我們讓它們static。此外,在不同的文件中可以允許存在擁有相同函數(shù)名的static函數(shù)。inline
inline是c99的特性。在c99中,inline是向編譯器建議,將被inline修飾的函數(shù)以內(nèi)聯(lián)的方式嵌入到調(diào)用這個函數(shù)的地方。而編譯器會判斷這樣做是否合適,以此最終決定是否這么做。static inline的優(yōu)劣
根據(jù)上面的定義可以知道,如果static inline關(guān)鍵字生效(因為只有編譯器有最終決定權(quán),我們只有建議權(quán),這點在后面會細講),static inline會以一種類似于宏定義的方式,將調(diào)用被static inline修飾的函數(shù)的語句替換為那個函數(shù)體對應(yīng)的指令,但實際上只是inline的作用,static作用其實是維護代碼的健壯性,實驗中會加以證明。 所以:
好處:減少調(diào)用函數(shù)時的開銷,如: 減少傳參時可能引起的壓棧出棧的開銷。
減少PC跳轉(zhuǎn)時對流水線的破壞。
壞處: * 代碼所占體積會更大。
實驗
目的:直觀證明上述優(yōu)點和缺點、探究static的作用
實驗一 :探究static inline對代碼起到的影響
實驗背景:硬件平臺:stm32f401re
IDE:stm32cubeMX、Keil(關(guān)掉指令優(yōu)化)
庫:LL庫(僅僅初始化芯片)
編程語言:C語言、Arm指令
實驗設(shè)計
嘗試構(gòu)造四個函數(shù),這四個函數(shù)都是實現(xiàn)對四個參數(shù)相加,然后將結(jié)果返回,不同之處在于它們被不同的關(guān)鍵字修飾。
分別為: int Normal_Add(int n1,int n2,int n3,int n4,int n5); static int Static_Add(int n1,int n2,int n3,int n4,int n5); __inline int Inline_Add(int n1,int n2,int n3,int n4,int n5); static __inline int StaticInline_Add(int n1,int n2,int n3,int n4,int n5);注:在Armcc編譯器的實現(xiàn)中,inline被實現(xiàn)為__inline
在這些函數(shù)中: Normal_Add是最簡單的函數(shù);
Static_Add是用static關(guān)鍵字修飾的函數(shù); Inline_Add是用inline關(guān)鍵字修飾的函數(shù);
StaticInline_Add是用static inline關(guān)鍵字修飾的函數(shù);
main.c
只羅列了關(guān)鍵部分
#include "funcTest.h" int main(void) {int i;i = Normal_Add(1,1,1,1,1);i = Static_Add(2,2,2,2,2);i = Inline_Add(3,3,3,3,3);i = StaticInline_Add(4,4,4,4,4);/*使用i,為了去掉編譯警告*/while (i > 0); }funcTest.h
#ifndef __FUNCTEST_H #define __FUNCTEST_H/*Normal_Add的聲明*/ int Normal_Add(int n1,int n2,int n3,int n4,int n5);/*其他三個函數(shù)的定義*/ static int Static_Add(int n1,int n2,int n3,int n4,int n5){return (n1+n2+n3+n4+n5);; }__inline int Inline_Add(int n1,int n2,int n3,int n4,int n5) {return (n1+n2+n3+n4+n5);; }static __inline int StaticInline_Add(int n1,int n2,int n3,int n4,int n5) {return (n1+n2+n3+n4+n5); }; #endiffuncTest.h文件其實涉及到一個問題,為什么Static_Add函數(shù)和Inline_Add函數(shù)還有StaticInline_Add函數(shù)要放在.h頭文件里呢?
理由: 考慮下這三個函數(shù)的作用,我們希望它們被其他文件訪問到嗎?顯然希望。那么,如果我們?nèi)绻裺tatic修飾的函數(shù)僅僅放在.c源文件中,其實其他的源文件就不能訪問到那幾個函數(shù)了。至于Inline_Add函數(shù),其實對于armcc編譯器,放不放在頭文件都可以。但是Inline關(guān)鍵字其實不建議單獨用,原因是不安全,最好配合static使用。這個在下一個實驗可以看到funcTest.h
#include <funcTest.h> /*Normal_Add函數(shù)的聲明部分*/ int Normal_Add(int n1,int n2,int n3,int n4,int n5) {return (n1+n2+n3+n4+n5); }實驗結(jié)果
通過armcc生成的反匯編結(jié)果,我們來看一看這幾個函數(shù)對應(yīng)的匯編指令有什么不同。
main.s
;注釋是自己加的;函數(shù)調(diào)用時:;R0-R3為參數(shù);剩下的一個變量在棧中;R0為返回值;R4為main函數(shù)里的變量i;Normal_Add函數(shù),加上子函數(shù)總共15條指令MOVS r0,#1MOV r3,r0MOV r2,r0MOV r1,r0;最后一個參數(shù)壓棧了STR r0,[sp,#0]BL Normal_AddMOV r4,r0;Static_Add函數(shù)MOVS r0,#2MOV r3,r0MOV r2,r0MOV r1,r0;最后一個參數(shù)壓棧了STR r0,[sp,#0]BL Static_AddMOV r4,r0;Inline_Add內(nèi)聯(lián)函數(shù),10條指令MOVS r0,#3MOV r1,r0MOV r2,r0MOV r3,r0MOV r5,r0;開始相加ADDS r6,r0,r1ADD r6,r6,r2ADD r6,r6,r3ADD r6,r6,r5;傳回變量iMOV r4,r6;StaticInline_Add內(nèi)聯(lián)函數(shù),10條指令MOVS r1,#4MOV r3,r1MOV r0,r1MOV r5,r1MOV r2,r1;開始相加ADDS r6,r1,r3ADD r6,r6,r0ADD r6,r6,r5ADD r6,r6,r2;傳回變量iMOV r4,r6NOPfunctest.s
Static_Add PROC;還有保存現(xiàn)場的操作,相當(dāng)浪費時間PUSH {r4,r5,lr}MOV r4,r0LDR r5,[sp,#0xc]ADDS r0,r4,r1ADD r0,r0,r2ADD r0,r0,r3ADD r0,r0,r5POP {r4,r5,pc}ENDPNormal_Add PROC;還有保存現(xiàn)場的操作,相當(dāng)浪費時間PUSH {r4,r5,lr}MOV r4,r0LDR r5,[sp,#0xc]ADDS r0,r4,r1ADD r0,r0,r2ADD r0,r0,r3ADD r0,r0,r5POP {r4,r5,pc}ENDP綜合以上兩個文件的代碼,我們不難發(fā)現(xiàn),非內(nèi)聯(lián)函數(shù)所用開銷顯然比內(nèi)聯(lián)函數(shù)的開銷大。因為非內(nèi)聯(lián)函數(shù)不但用的指令多,還訪問內(nèi)存了,還用跳轉(zhuǎn)指令破壞了流水線。所以肯定會慢很多。使用keil的debug功能,定量算它們所用時間差異。結(jié)果如下:
- Normal_Add函數(shù)與static_Add函數(shù):
0.000037s = 37us - Inline_Add函數(shù)與StaticInline_Add函數(shù):
0.000012s = 12us
雖然在這里,沒有體現(xiàn)內(nèi)聯(lián)函數(shù)內(nèi)存開銷大的特點。但請想一想,如果一個內(nèi)聯(lián)函數(shù)被調(diào)用多次,那么這個函數(shù)就將被內(nèi)聯(lián)多次,代碼就會被復(fù)制多次;但是一個非內(nèi)聯(lián)函數(shù)被調(diào)用多次,卻并不會被復(fù)制多次。所以內(nèi)聯(lián)函數(shù)往往內(nèi)存開銷會大一點。
實驗二 :探究static inline和inline的區(qū)別
在講這個之前,其實我們需要知道一點。就是static inline關(guān)鍵字和inline關(guān)鍵字無法決定被關(guān)鍵字所修飾的函數(shù)是否最后真正會被內(nèi)聯(lián)。我們其實只有建議權(quán),只有armcc編譯器才可以決定函數(shù)最后是否真正會被內(nèi)聯(lián)。
參見Armcc User Guide原文:
inline functions in C99The C99 keyword inline hints to the compiler that invocations of a function qualified with inline are to be expanded inline. For example: c inline int max(int a, int b) { return (a > b) ? a : b; } The compiler inlines a function qualified with inline only if it is reasonable to do so. It is free to ignore the hint if inlining the function adversely affects performance. Note :The __inline keyword is available in C90.Note: The semantics of inline in C99 are different to the semantics of inline in Standard C++.
Comiler decisions on function inliningWhen function inlining is enabled, the compiler uses a complex decision tree to decide if a function is to be inlined.The following simplified algorithm is used: 1. If the function is qualified with forceinline, the function is inlined if it is possible to do so. 2. If the function is qualified with inline and the option --forceinline is selected, the function is inlined if it is possible to do so. If the function is qualified with inline and the option --forceinline is not selected, the function is inlined if it is practical to do so. 3. If the optimization level is -O2 or higher, or --autoinline is specified, the compiler automatically inlines functions if it is practical to do so, even if you do not explicitly give a hint that function inlining is wanted. When deciding if it is practical to inline a function, the compiler takes into account several other criteria, such as: ? The size of the function, and how many times it is called. ? The current optimization level. ? Whether it is optimizing for speed (-Otime) or size (-Ospace). ? Whether the function has external or static linkage. ? How many parameters the function has. ? Whether the return value of the function is used. Ultimately, the compiler can decide not to inline a function, even if the function is qualified with forceinline. As a general rule:
? Smaller functions stand a better chance of being inlined.
? Compiling with -Otime increases the likelihood that a function is inlined.
? Large functions are not normally inlined because this can adversely affect code density and performance.
A recursive function is never inlined into itself, even if __forceinline is used.
濃縮成一句話:
開發(fā)者決定不了一個函數(shù)是否被內(nèi)聯(lián),開發(fā)者只有建議權(quán),只有編譯器具有決定權(quán)。這就造成了一個很disturbing的事情:除非你看到一個函數(shù)的反匯編代碼,否則你很難確定他是不是內(nèi)聯(lián)函數(shù)。
下面,我們來看看一個被static inline修飾的非內(nèi)聯(lián)函數(shù):
static __inline int Fake_StaticInline_Add(int n1,int n2,int n3,int n4,int n5) {/*只是為了多湊幾條指令*/n1++;n1++;n1++;n1++;n1++;n1++;n1++;n1++;n1++;n1++;n1++;return (n1+n2+n3+n4+n5);; }這個函數(shù)我們把他放在main.c中,并在main函數(shù)這樣調(diào)用:i = Fake_StaticInline_Add(6,6,6,6,6);。現(xiàn)在我們來看看他的反匯編代碼:
MOVS r0,#5MOV r3,r0MOV r2,r0MOV r1,r0STR r0,[sp,#0]BL Fake_StaticInline_AddMOV r4,r0NOP驚不驚喜,意不意外? 這個函數(shù)居然是被調(diào)用了的,而并沒有被內(nèi)聯(lián)到調(diào)用它的地方。那么,為什么這個函數(shù)沒變成內(nèi)聯(lián)函數(shù)呢?
我推斷是因為我在這個函數(shù)中寫入了太多指令,編譯器判斷如果它變成內(nèi)聯(lián)函數(shù),會極大占用空間,所以不將它編譯成內(nèi)聯(lián)函數(shù)。
但你還記得我們之前把Inline_Add函數(shù)放在哪里嗎?放在了一個頭文件。試想,如果這個Inline_Add在沒有被編譯成內(nèi)聯(lián)函數(shù)的情況下,被include到了多個源文件中,勢必會產(chǎn)生函數(shù)重復(fù)定義的問題。 因此,我們要再加一個關(guān)鍵字static,才能避免這個問題。
結(jié)論
至此,我們直觀地理解了static inline的特點,并知道了為什么static和inline要聯(lián)合使用。
總結(jié)一下static inline什么時候用比較好: 當(dāng)所修飾的函數(shù)語句較少時,尤其是只有一兩條語句的函數(shù)。 當(dāng)你所修飾的函數(shù)不是遞歸函數(shù),而是正常的函數(shù)時。因為遞歸式不支持內(nèi)聯(lián)的。
感想
終于粗略地寫完了。這個我原以為一個小時就能完成地實驗,實際上做了一下午......
一句話與君共勉:
總結(jié)
以上是生活随笔為你收集整理的、简述global关键字的作用_详解static inline关键字的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基建管控系统_科技|电力北斗科技创新为数
- 下一篇: 8086的两种工作模式_Lora自组网网