真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

嵌入式C語(yǔ)言自我修養(yǎng)10:內(nèi)聯(lián)函數(shù)探究

10.1 屬性聲明:noinline & always_inline

這一節(jié),接著講 attribute 屬性聲明,attribute可以說(shuō)是 GNU C 最大的特色。我們接下來(lái)繼續(xù)講一下跟內(nèi)聯(lián)函數(shù)相關(guān)的兩個(gè)屬性:noinline 和 always_inline。這兩個(gè)屬性的用途是告訴編譯器:編譯時(shí),對(duì)我們指定的函數(shù)內(nèi)聯(lián)展開(kāi)或不展開(kāi)。它們的使用方法如下。

成都創(chuàng)新互聯(lián)公司2013年開(kāi)創(chuàng)至今,先為靈璧等服務(wù)建站,靈璧等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為靈璧企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

static  inline __attribute__((noinline)) int func();
static  inline __attribute__((always_inline)) int func();

內(nèi)聯(lián)函數(shù)使用 inline 聲明即可,有時(shí)候還會(huì)用 static 和 extern 修飾。使用 inline 聲明一個(gè)內(nèi)聯(lián)函數(shù),和使用關(guān)鍵字 register 聲明一個(gè)變量一樣,只是建議編譯器在編譯時(shí)內(nèi)聯(lián)展開(kāi)。使用關(guān)鍵字 register 修飾變量時(shí),只是建議編譯器在給變量分配存儲(chǔ)空間時(shí),將這個(gè)變量放到寄存器里,這樣,程序的運(yùn)行效率會(huì)更高。那編譯器會(huì)不會(huì)放呢?編譯器就要根據(jù)寄存器資源緊不緊張,這個(gè)變量用得頻不頻繁來(lái)做權(quán)衡。

同樣,當(dāng)一個(gè)函數(shù)使用 inline 關(guān)鍵字修飾,編譯器在編譯時(shí)一定會(huì)內(nèi)聯(lián)展開(kāi)嗎?未必。編譯器也會(huì)根據(jù)實(shí)際情況,比如函數(shù)體大小、函數(shù)體內(nèi)是否有循環(huán)結(jié)構(gòu)、是否有指針、是否有遞歸、函數(shù)調(diào)用是否頻繁來(lái)做決定。比如 GCC 編譯器,一般是不會(huì)對(duì)內(nèi)聯(lián)函數(shù)展開(kāi)的,只有當(dāng)編譯優(yōu)化選項(xiàng)開(kāi)到 -O2 以上,才會(huì)考慮是否內(nèi)聯(lián)展開(kāi)。當(dāng)我們使用 noinline 和 always_inline 對(duì)一個(gè)內(nèi)聯(lián)函數(shù)作了屬性聲明后,編譯器的編譯行為就變得確定了。使用 noinline 聲明,就是告訴編譯器,不要展開(kāi);使用 always_inline 屬性聲明,就是告訴編譯器,要內(nèi)聯(lián)展開(kāi)。

什么是內(nèi)聯(lián)展開(kāi)呢?我們不得不說(shuō)一下內(nèi)聯(lián)函數(shù)的基礎(chǔ)知識(shí)。

10.2 什么是內(nèi)聯(lián)函數(shù)

函數(shù)調(diào)用開(kāi)銷

說(shuō)起內(nèi)聯(lián)函數(shù),又不得不說(shuō)函數(shù)調(diào)用開(kāi)銷。一個(gè)函數(shù)在執(zhí)行過(guò)程中,如果需要調(diào)用其它函數(shù),一般會(huì)執(zhí)行下面這個(gè)過(guò)程。

  • 保存當(dāng)前函數(shù)現(xiàn)場(chǎng)
  • 跳到調(diào)用函數(shù)執(zhí)行
  • 恢復(fù)當(dāng)前函數(shù)現(xiàn)場(chǎng)
  • 繼續(xù)執(zhí)行當(dāng)前函數(shù)

比如一個(gè) ARM 程序,在一個(gè)函數(shù) f1() 中,我們對(duì)一些數(shù)據(jù)進(jìn)行處理,運(yùn)算結(jié)果暫時(shí)保存在 R0 寄存器中。接著要調(diào)用另外一個(gè)函數(shù) f2(),調(diào)用結(jié)束后,接著返回到 f1() 函數(shù)中繼續(xù)處理數(shù)據(jù)。如果我們?cè)?f2() 函數(shù)中使用到 R0 這個(gè)寄存器(用于保存函數(shù)的返回值),此時(shí)就會(huì)改變 R0 寄存器中的值,那么就篡改了 f1() 函數(shù)中的暫存運(yùn)算結(jié)果。當(dāng)我們返回到 f1() 函數(shù)中繼續(xù)進(jìn)行運(yùn)算時(shí),結(jié)果肯定不正確。

那怎么辦呢?很簡(jiǎn)單,在跳到 f2() 執(zhí)行之前,先把 R0 寄存器的值保存到堆棧中,f() 函數(shù)執(zhí)行結(jié)束后,再將堆棧中的值恢復(fù)到 R0 寄存器中,這樣 f1() 函數(shù)就可以接著繼續(xù)執(zhí)行了,就跟什么事情都沒(méi)發(fā)生過(guò)一樣。

這種方法證明是 OK 的,現(xiàn)代計(jì)算機(jī)系統(tǒng),無(wú)論是什么架構(gòu)和指令集,都是采用這種方法。雖然麻煩了點(diǎn),但至少能解決問(wèn)題,無(wú)非就是多花點(diǎn)代價(jià),需要不斷地保存現(xiàn)場(chǎng)、恢復(fù)現(xiàn)場(chǎng),這就是函數(shù)調(diào)用帶來(lái)的開(kāi)銷。

內(nèi)聯(lián)函數(shù)的好處

對(duì)于一般的函數(shù)調(diào)用,這種方法是沒(méi)有問(wèn)題的。但對(duì)于一些極端情況,比如說(shuō)一個(gè)函數(shù)很小,函數(shù)體內(nèi)只有一行代碼,而且被大量頻繁的調(diào)用。如果每次調(diào)用,都不斷地保存現(xiàn)場(chǎng),執(zhí)行時(shí)卻發(fā)現(xiàn)函數(shù)只有一行代碼,又要恢復(fù)現(xiàn)場(chǎng),往往造成函數(shù)開(kāi)銷比較大,性價(jià)比不高。這就跟你去五星級(jí)飯店訂個(gè)餐位吃飯一樣,VIP 包間、刀叉餐具、空調(diào)、服務(wù)人員都準(zhǔn)備好了,你到了之后只要了一碗面條,吃完之后抹嘴走人,而且一天三頓你都這么干,你說(shuō)服務(wù)員煩不煩?

函數(shù)調(diào)用也是如此。有些函數(shù)很小,而且調(diào)用頻繁,調(diào)用開(kāi)銷大,算下來(lái)性價(jià)比不高。我們就可以將這個(gè)函數(shù)聲明為內(nèi)聯(lián)函數(shù)。編譯器在編譯過(guò)程中遇到內(nèi)聯(lián)函數(shù)時(shí),像宏一樣,將內(nèi)聯(lián)函數(shù)直接在調(diào)用處展開(kāi)。這樣做的好處就是減少了函數(shù)調(diào)用開(kāi)銷,直接執(zhí)行內(nèi)聯(lián)函數(shù)展開(kāi)的代碼,不用再保存現(xiàn)場(chǎng)、恢復(fù)現(xiàn)場(chǎng)。

10.3 內(nèi)聯(lián)函數(shù)與宏

看到這里,可能就有人納悶了,內(nèi)聯(lián)函數(shù)既然跟宏的功能差不多,那為什么不直接定義一個(gè)宏,而去定義一個(gè)內(nèi)聯(lián)函數(shù)呢?

存在即合理,內(nèi)聯(lián)函數(shù)既然在 C 語(yǔ)言中廣泛應(yīng)用,自然有它存在的道理。相對(duì)于宏,內(nèi)聯(lián)函數(shù)有以下幾個(gè)優(yōu)勢(shì)。

  • 參數(shù)類型檢查。內(nèi)聯(lián)函數(shù)雖然具有宏的展開(kāi)特性,但其本質(zhì)仍是函數(shù),編譯過(guò)程中,編譯器仍可以對(duì)其進(jìn)行參數(shù)檢查,而宏就不具備這個(gè)功能。
  • 便于調(diào)試。函數(shù)支持的調(diào)試功能有斷點(diǎn)、單步……,內(nèi)聯(lián)函數(shù)也同樣可以。
  • 返回值。內(nèi)聯(lián)函數(shù)有返回值,返回一個(gè)結(jié)果給調(diào)用者。這個(gè)優(yōu)勢(shì)是相對(duì)于 ANSI C 說(shuō)的。不過(guò)現(xiàn)在宏也可以有返回值和類型了,比如前面我們使用語(yǔ)句表達(dá)式定義的宏。
  • 接口封裝。有些內(nèi)聯(lián)函數(shù)可以用來(lái)封裝一個(gè)接口,而宏不具備這個(gè)特性。

10.4 編譯器對(duì)內(nèi)聯(lián)函數(shù)的處理

前面也講過(guò),我們雖然可以通過(guò) inline 關(guān)鍵字,將一個(gè)函數(shù)聲明為內(nèi)聯(lián)函數(shù),但編譯器不一定會(huì)對(duì)這個(gè)內(nèi)聯(lián)函數(shù)展開(kāi)處理。編譯器也要進(jìn)行評(píng)估,權(quán)衡展開(kāi)和不展開(kāi)的利弊。

內(nèi)聯(lián)函數(shù)并不是完美無(wú)瑕,也有一些缺點(diǎn)。比如說(shuō),會(huì)增大程序的體積。如果在一個(gè)文件中多次調(diào)用內(nèi)聯(lián)函數(shù),多次展開(kāi),那整個(gè)程序的體積就會(huì)變大,在一定程度上,會(huì)造成 CPU 的取址效率降低,程序執(zhí)行效率降低。函數(shù)的作用之一就是提高代碼的復(fù)用性,我們將常用的一些代碼或代碼塊封裝成函數(shù),進(jìn)行模塊化編程,而內(nèi)聯(lián)函數(shù)往往是降低了函數(shù)的復(fù)用性。所以編譯器在對(duì)內(nèi)聯(lián)函數(shù)作展開(kāi)處理時(shí),除了檢測(cè)用戶定義的內(nèi)聯(lián)函數(shù)內(nèi)部是否有指針、循環(huán)、遞歸外,還會(huì)在函數(shù)執(zhí)行效率和函數(shù)調(diào)用開(kāi)銷之間進(jìn)行權(quán)衡。一般來(lái)講,判斷對(duì)一個(gè)內(nèi)聯(lián)函數(shù)到底展不展開(kāi),從程序員的角度,主要考慮以下幾個(gè)因素。

  • 函數(shù)體積小且調(diào)用頻繁
  • 函數(shù)體內(nèi)無(wú)遞歸、循環(huán)等語(yǔ)句
  • 函數(shù)本身作為一個(gè)函數(shù)指針賦值在別處被引用
  • 函數(shù)和caller是否在同一個(gè)文件內(nèi)

當(dāng)我們認(rèn)為一個(gè)函數(shù)體積小,而且被大量頻繁調(diào)用,應(yīng)該做內(nèi)聯(lián)展開(kāi)時(shí),就可以使用 static inline 關(guān)鍵字修飾它。但編譯器會(huì)不會(huì)作內(nèi)聯(lián)展開(kāi),編譯器也會(huì)有自己的權(quán)衡。如果你想告訴編譯器一定要展開(kāi),或者不作展開(kāi),就可以使用 noinline 或 always_inline 對(duì)函數(shù)作一個(gè)屬性聲明。

//inline.c
static inline 
__attribute__((always_inline))  int func(int a)
{
    return a+1;
}

static inline void print_num(int a)
{
    printf("%d\n",a);
}
int main(void)
{
    int i;
    i=func(3);
    print_num(10);
    return 0;
}

在這個(gè)程序中,我們分別定義兩個(gè)內(nèi)聯(lián)函數(shù) func() 和 print_num(),然后使用 always_inline 對(duì) func() 函數(shù)進(jìn)行屬性聲明。接下來(lái),我們對(duì)生成的可執(zhí)行文件 a.out 作反匯編處理,其匯編代碼如下。

$ arm-linux-gnueabi-gcc -o a.out inline.c
$ arm-linux-gnueabi-objdump -D a.out 
00010438 :
   10438:    e92d4800    push    {fp, lr}
   1043c:    e28db004    add fp, sp, #4
   10440:    e24dd008    sub sp, sp, #8
   10444:    e50b0008    str r0, [fp, #-8]
   10448:    e51b1008    ldr r1, [fp, #-8]
   1044c:    e59f000c    ldr r0, [pc, #12]
   10450:    ebffffa2    bl  102e0 
   10454:    e1a00000    nop ; (mov r0, r0)
   10458:    e24bd004    sub sp, fp, #4
   1045c:    e8bd8800    pop {fp, pc}
   10460:    0001050c    andeq   r0, r1, ip, lsl #10

00010464 
: 10464: e92d4800 push {fp, lr} 10468: e28db004 add fp, sp, #4 1046c: e24dd008 sub sp, sp, #8 10470: e3a03003 mov r3, #3 10474: e50b3008 str r3, [fp, #-8] 10478: e51b3008 ldr r3, [fp, #-8] 1047c: e2833001 add r3, r3, #1 10480: e50b300c str r3, [fp, #-12] 10484: e3a0000a mov r0, #10 10488: ebffffea bl 10438 1048c: e3a03000 mov r3, #0 10490: e1a00003 mov r0, r3 10494: e24bd004 sub sp, fp, #4 10498: e8bd8800 pop {fp, pc}

通過(guò)反匯編代碼可以看到,因?yàn)槲覀儗?duì) func() 函數(shù)作了 always_inline 屬性聲明,所以編譯器在編譯過(guò)程中,對(duì)于 main()函數(shù)調(diào)用 func(),會(huì)直接在調(diào)用處展開(kāi)。

10470:    e3a03003    mov r3, #3
   10474:    e50b3008    str r3, [fp, #-8]
   10478:    e51b3008    ldr r3, [fp, #-8]
   1047c:    e2833001    add r3, r3, #1
   10480:    e50b300c    str r3, [fp, #-12]

而對(duì)于 print_num() 函數(shù),雖然我們對(duì)其作了內(nèi)聯(lián)聲明,但編譯器并沒(méi)有對(duì)其作內(nèi)聯(lián)展開(kāi),而是當(dāng)作一個(gè)普通函數(shù)對(duì)待。還有一個(gè)注意的細(xì)節(jié)是,當(dāng)編譯器對(duì)內(nèi)聯(lián)函數(shù)作展開(kāi)處理時(shí),會(huì)直接在調(diào)用處展開(kāi)內(nèi)聯(lián)函數(shù)的代碼,不再給 func() 函數(shù)本身生成單獨(dú)的匯編代碼。這是因?yàn)槠渌{(diào)用該函數(shù)的位置都作了內(nèi)聯(lián)展開(kāi),沒(méi)必要再去生成。在這個(gè)例子中,我們發(fā)現(xiàn)就沒(méi)有給 func() 函數(shù)本身生成單獨(dú)的匯編代碼,編譯器只給 print_num() 函數(shù)生成了獨(dú)立的匯編代碼。

10.5 思考:內(nèi)聯(lián)函數(shù)為什么常使用 static 修飾?

在 Linux 內(nèi)核中,你會(huì)看到大量的內(nèi)聯(lián)函數(shù)定義在頭文件中,而且常常使用 static 修飾。

為什么 inline 函數(shù)經(jīng)常使用 static 修飾呢?這個(gè)問(wèn)題在網(wǎng)上也討論了很久,聽(tīng)起來(lái)各有道理,從 C 語(yǔ)言到 C++,甚至有人還拿出了 Linux 內(nèi)核作者 Linus 作者關(guān)于對(duì) static inline 的解釋:

"static inline" means "we have to have this function, if you use it, but don't inline it, then make a static version of it in this compilation unit". "extern inline" means "I actually have an extern for this function, but if you want to inline it, here's the inline-version".

我的理解是這樣的:內(nèi)聯(lián)函數(shù)為什么要定義在頭文件中呢?因?yàn)樗且粋€(gè)內(nèi)聯(lián)函數(shù),可以像宏一樣使用,任何想使用這個(gè)內(nèi)聯(lián)函數(shù)的源文件,不必親自再去定義一遍,直接包含這個(gè)頭文件,即可像宏一樣使用。那為什么還要用 static 修飾呢?因?yàn)槲覀兪褂?inline 定義的內(nèi)聯(lián)函數(shù),編譯器不一定會(huì)內(nèi)聯(lián)展開(kāi),那么當(dāng)多個(gè)文件都包含這個(gè)內(nèi)聯(lián)函數(shù)的定義時(shí),編譯時(shí)就有可能報(bào)重定義錯(cuò)誤。而使用 static 修飾,可以將這個(gè)函數(shù)的作用域局限在各自本地文件內(nèi),避免了重定義錯(cuò)誤。理解了這兩點(diǎn),就能夠看懂 Linux 內(nèi)核頭文件中定義的大部分內(nèi)聯(lián)函數(shù)了。至于其它的一些內(nèi)聯(lián)函數(shù)定義,基本上沒(méi)怎么遇到過(guò),就不再贅述了。

本教程根據(jù) C語(yǔ)言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f


文章名稱:嵌入式C語(yǔ)言自我修養(yǎng)10:內(nèi)聯(lián)函數(shù)探究
本文網(wǎng)址:http://weahome.cn/article/gspjie.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部