[cpp deep dive]一些奇怪的關(guān)鍵字_不那么奇怪的inline

step 0.1 先來1題

關(guān)于c++的inline關(guān)鍵字,以下說法正確的是(4.)
1. 使用inline關(guān)鍵字的函數(shù)會被編譯器在調(diào)用處展開                      <--------不一定都會辕狰,有些會被編譯器拒絕味赃。
2. 頭文件中可以包含inline函數(shù)的聲明                                 <--------可以从祝?會有警告(所以我們姑且認(rèn)為不可以)
3. 可以在同一個項目的不同源文件內(nèi)定義函數(shù)名相同但實現(xiàn)不同的inline函數(shù)  <--------可以是可以漆撞,也編譯通過苞慢,但會產(chǎn)生意想不到的結(jié)果艾扮,所以還是不推薦.
4. 定義在Class聲明內(nèi)的成員函數(shù)默認(rèn)是inline函數(shù)                      <--------ok
5. 優(yōu)先使用Class聲明內(nèi)定義的inline函數(shù)        <----------|
6. 優(yōu)先使用Class實現(xiàn)的內(nèi)inline函數(shù)的實現(xiàn)      <----------|--這兩個其實沒有誰優(yōu)先的掉盅,例如函數(shù)體太大有循環(huán)就不推薦使用inline

step 0.2 函數(shù)的調(diào)用過程

主要是通過這個來深♂入♂理解為什么函數(shù)調(diào)用存在一定開銷。
函數(shù)的調(diào)用過程主要有(模糊)

  • 一個大坑 - movq為AT&T指令赃阀,而我們課本上學(xué)的是MASM指令霎肯,兩者的方向是反的(我鈤了你大爺!)
    AT&T的mov語法(movq為復(fù)制8個字節(jié)榛斯,雙字) - movq src,dest

  • call指令 - 主要是2部分操作組成:pushl %eip & ljmp

  • ret指令 - 主要是 popl %eip.

  • 以下兩條指令用于切換堆棧上下文.

    • enter指令 -
      等價于
push ebp
ebp ← esp
  • leave指令 -
    等價于
esp ← ebp
pop ebp

(其中 esp - 棧頂指針观游;ebp - 棧基址指針)

  • 下面詳細(xì)闡述一下函數(shù)調(diào)用的具體過程.
    示例代碼 test_func_.c
int foo2(){ <---------為啥要兩個函數(shù)驮俗,主要是因為假如下面的foo不調(diào)用其他函數(shù)的話懂缕,在foo的匯編代碼里就不會有第30行了,即棧頂指針就不用了意述,想想也是合理提佣,這樣減少了一些代碼量
    int kkkkk[4];
    return 0;
}
int foo(int k1, int k2, int k3){
    int s1[5] = {1,2,3,4,5};
    int s = 1;
    foo2();
    return k1+s;
}
int main(){
    int s=5;
    int s2=40;
    foo(400, 500, 600);
    return 0;
}

gcc -S test_func_.c -o test_func_.s的匯編結(jié)果:(不要怕,只看最關(guān)鍵的地方)

  1     .file   "test_func_.c"                                                      
  2     .text                                                                       
  3     .globl  foo2                                                                
  4     .type   foo2, @function                                                     
  5 foo2:              <---------foo2加進(jìn)來的原因是為了更好地展示調(diào)用函數(shù)的一般流程                                                            
  6 .LFB0:                                                                          
  7     .cfi_startproc                                                              
  8     pushq   %rbp                                                                
  9     .cfi_def_cfa_offset 16                                                      
 10     .cfi_offset 6, -16                                                          
 11     movq    %rsp, %rbp                                                          
 12     .cfi_def_cfa_register 6                                                     
 13     movl    $0, %eax                                                            
 14     popq    %rbp                                                                
 15     .cfi_def_cfa 7, 8                                                           
 16     ret                                                                         
 17     .cfi_endproc                                                                
 18 .LFE0:                                                                          
 19     .size   foo2, .-foo2                                                        
 20     .globl  foo                                                                 
 21     .type   foo, @function                                                      
 22 foo:                           <---------主要關(guān)心這個                                                 
 23 .LFB1:                                                                          
 24     .cfi_startproc                                                              
 25     pushq   %rbp                <------------舊基址進(jìn)棧                                
 26     .cfi_def_cfa_offset 16                                                      
 27     .cfi_offset 6, -16                                                          
 28     movq    %rsp, %rbp           <------------新基址為當(dāng)前的棧頂                                    
 29     .cfi_def_cfa_register 6                                                     
 30     subq    $64, %rsp               <-----------當(dāng)本函數(shù)有調(diào)用其他函數(shù)的動作時荤崇,才會把棧頂向下移拌屏,親測。例如把上面的foo2及相關(guān)調(diào)用刪除术荤,這句就沒了倚喂。        
 31     movl    %edi, -52(%rbp)     <-----------一系列的操作都是用棧基址寄存器+偏移量來操作的                                                
 32     movl    %esi, -56(%rbp)                                                     
 33     movl    %edx, -60(%rbp)                                                     
 34     movl    $1, -32(%rbp)                                                       
 35     movl    $2, -28(%rbp)                                                       
 36     movl    $3, -24(%rbp)                                                       
 37     movl    $4, -20(%rbp)                                                       
 38     movl    $5, -16(%rbp)                                                       
 39     movl    $1, -36(%rbp)                                                       
 40     movl    $0, %eax                                                            
 41     call    foo2                                                                                                                                                                           
 42     movl    -36(%rbp), %eax
 42     movl    -36(%rbp), %eax                                                     
 43     movl    -52(%rbp), %edx                                                     
 44     addl    %edx, %eax                                                          
 45     leave                                                                       
 46     .cfi_def_cfa 7, 8                                                           
 47     ret                                                                         
 48     .cfi_endproc                                                                                                                                                                           
 49 .LFE1:                                                                          
 50     .size   foo, .-foo                                                          
 51     .globl  main                                                                
 52     .type   main, @function                                                     
 53 main:                                                                           
 54 .LFB2:                                                                          
 55     .cfi_startproc                                                              
 56     pushq   %rbp                                                                
 57     .cfi_def_cfa_offset 16                                                      
 58     .cfi_offset 6, -16                                                          
 59     movq    %rsp, %rbp                                                          
 60     .cfi_def_cfa_register 6                                                     
 61     subq    $16, %rsp                                                           
 62     movl    $5, -8(%rbp)                                                        
 63     movl    $40, -4(%rbp)                                                       
 64     movl    $600, %edx                                                          
 65     movl    $500, %esi                                                          
 66     movl    $400, %edi                                                          
 67     call    foo                                                                 
 68     movl    $0, %eax                                                            
 69     leave
 70     .cfi_def_cfa 7, 8                                                           
 71     ret                                                                         
 72     .cfi_endproc                                                                
 73 .LFE2:                                                                          
 74     .size   main, .-main                                                        
 75     .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"                        
 76     .section    .note.GNU-stack,"",@progbits

現(xiàn)在的編譯器優(yōu)化呀,我真是too young端圈!
總結(jié)如下面這個圖:

堆棧分析 [如果太小焦读,請點這個](https://www.processon.com/view/57938177e4b06834741b7e17)

安利一下這個在線流程圖編輯

總結(jié)一下,

  • 函數(shù)調(diào)用通過call(調(diào)用處)/ret(被調(diào)用函數(shù)內(nèi)部)實現(xiàn)ip入棧和跳轉(zhuǎn).
  • 進(jìn)入函數(shù)的棧上下文切換是在函數(shù)體內(nèi)部進(jìn)行棽杖ǎ基址入棧和棿;危基址賦值.
  • 進(jìn)入函數(shù)的棧上下文切換有可能不對%esp進(jìn)行減.
  • 退出函數(shù)時的棧上下文切換通過leave指令實現(xiàn).

反正總之,只要是函數(shù)調(diào)用至少需要多4~5條指令宴倍,假如這個函數(shù)本身的指令很少张症,那是十分影響效率的。

step 1. 深入

  • 什么是內(nèi)聯(lián)函數(shù)?(為什么要用內(nèi)聯(lián))
    函數(shù)的調(diào)用存在一定代價鸵贬,如果函數(shù)本身執(zhí)行的邏輯很少(假設(shè)一個極端情況就是函數(shù)本身執(zhí)行的時間小于函數(shù)的調(diào)用代價)俗他,這樣就造成效率低下。(*)
    C中這種情境下常用宏來解決阔逼。但宏本身又缺少參數(shù)檢查等機(jī)制兆衅,難以debug.
    inline函數(shù)就在這種情況下采用,即結(jié)合了宏和函數(shù)兩種機(jī)制的優(yōu)點.
    • inline vs. 宏:
      • 宏不能:
        預(yù)處理器不能進(jìn)行類型安全檢查嗜浮,或者進(jìn)行自動類型轉(zhuǎn)換羡亩。
        對象外的宏展開不能訪問對象的私有成員。
      • 而對象外的內(nèi)聯(lián)函數(shù)調(diào)用卻可以:
        安全檢查:內(nèi)聯(lián)函數(shù)參數(shù)在編譯階段進(jìn)行安全檢查危融。
        自動類型轉(zhuǎn)換:內(nèi)聯(lián)函數(shù)參數(shù)可以進(jìn)行自動類型轉(zhuǎn)換夕春。
        訪問私有成員:這個是宏無法辦到的。
        編譯器將在調(diào)用處展開专挪,省去函數(shù)調(diào)用的代價。
  • 如何使用內(nèi)聯(lián)函數(shù)?
    • 定義在類聲明中的函數(shù)將默認(rèn)被認(rèn)為是內(nèi)聯(lián)的片排。
    • 編譯器可能拒絕內(nèi)聯(lián):函數(shù)體過大或者存在循環(huán)/遞歸等寨腔。
    • 在函數(shù)定義處寫inline關(guān)鍵字,僅在聲明處寫inline不會起作用
      (UPDATE:而且在聲明處寫inline還會造成一個警告 - inline function ‘void base::foo()’ used but never defined)溉潭。
    • ** 多文件調(diào)用內(nèi)聯(lián):如果內(nèi)聯(lián)函數(shù)的定義不在本文件中(例如base.h聲明了一個類废封,base.cc定義其的某個成員函數(shù)為內(nèi)聯(lián)虑绵,而在main.cc中調(diào)用base的某個對象的此成員函數(shù))將可能**產(chǎn)生一個鏈接階段的錯誤,類似于
      main.cc:(.text+0x21): undefined reference to `base::foo()'
      collect2: error: ld returned 1 exit status
      
      也可能沒有出錯乾蛤,但會產(chǎn)生歧義,跟編譯器實現(xiàn)有關(guān)(捅僵?)家卖。例如附1 PHASE_4的例子。
    • 解決上一點最好的辦法是庙楚,把inline函數(shù)的定義搬到.h中上荡,誰需要調(diào)用inline誰就include我這個.h免得出現(xiàn)意想不到的結(jié)果。(這也是實際場景中經(jīng)常做的馒闷。)

附1.

main.cc

#include "../common.h"http://include stdio.h and so on.
#include "base.h"
//inline void base::foo(){ kk = 200001; }//<-----------在PHASE_2/3去掉注釋
int test(base & kk);
int main()
{
    base b(1000);
    b.foo();
    b.bar();
    test(b);
    b.bar();
    return 0;
}

test.cc

#include "base.h"
//inline void base::foo(){ kk = 100001; } //<-----------在PHASE_3/4去掉注釋
int test(base & kk){
    kk.foo();
    return 0;
}

base.cc

#include "base.h"
inline void base::foo(){ kk = 10; }
base::base(int s) 
: kk(s){

}

base.h

#ifndef BASE_H
#define BASE_H
#include "../common.h"
class base{
    public:
        base(int s);
        
        //inline void foo();
        /*
        base.h:8:18: error: inline function ‘void base::foo()’ used but never defined [-Werror]
        inline void foo();
                  ^
        cc1plus: all warnings being treated as errors
        */
        void foo();
        void bar(){ printf("%d\n",kk);}
    private:
        int kk;
};

#endif

Makefile(順便復(fù)習(xí)了一把Makfile酪捡!@H鳌!)

target=test
main_src=main.cc test.cc
base_src=base.cc
base_target=$(patsubst %.cc,%.o,$(base_src))
CXX=g++
CXXFLAGS=-Werror
.PHONY:clean
all:$(main_src) $(base_target)
    $(CXX) $(CXXFLAGS) $^ -o $(target)


$(base_target):$(base_src) #如果Make命令運行時沒有指定目標(biāo),默認(rèn)會執(zhí)行Makefile文件的第一個目標(biāo)逛薇。
    $(CXX) -c $^ -o $@

clean:
    rm -rf $(target) $(base_target)
  • PHASE_1 main.cc & test.cc都沒有inline base::foo的定義,出錯捺疼。
/tmp/ccINLKCW.o: In function `main':
main.cc:(.text+0x21): undefined reference to `base::foo()'
/tmp/cc1MfEWZ.o: In function `test(base&)':
test.cc:(.text+0x14): undefined reference to `base::foo()'
collect2: error: ld returned 1 exit status
make: *** [all] Error 1
  • PHASE_2 main.cc中的注釋行去掉注釋:
root@vm1:/home/work/share/toys/CSE274.me/02_Cpp_Intro/testinline# ./test
200001
200001
  • PHASE_3 test.cc中的注釋行也去掉注釋,同時main.cc保持與PHASE_2一致永罚。
root@vm1:/home/work/share/toys/CSE274.me/02_Cpp_Intro/testinline# ./test
200001
200001
  • PHASE_4 只保留test.cc的inline定義.
root@vm1:/home/work/share/toys/CSE274.me/02_Cpp_Intro/testinline# ./test
100001
100001

這里PHASE_4啤呼,也是inline函數(shù)定義不在本文件中,但沒有出現(xiàn)ld錯誤尤蛮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媳友,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子产捞,更是在濱河造成了極大的恐慌醇锚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯临,死亡現(xiàn)場離奇詭異焊唬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)看靠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門赶促,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挟炬,你說我怎么就攤上這事鸥滨。” “怎么了谤祖?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵婿滓,是天一觀的道長。 經(jīng)常有香客問我粥喜,道長凸主,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任额湘,我火速辦了婚禮卿吐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锋华。我一直安慰自己嗡官,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布毯焕。 她就那樣靜靜地躺著谨湘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上紧阔,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天坊罢,我揣著相機(jī)與錄音,去河邊找鬼擅耽。 笑死活孩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乖仇。 我是一名探鬼主播憾儒,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乃沙!你這毒婦竟也來了起趾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤警儒,失蹤者是張志新(化名)和其女友劉穎训裆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜀铲,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡边琉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了记劝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片变姨。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厌丑,靈堂內(nèi)的尸體忽然破棺而出定欧,到底是詐尸還是另有隱情,我是刑警寧澤怒竿,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布忧额,位于F島的核電站,受9級特大地震影響愧口,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜类茂,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一耍属、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巩检,春花似錦厚骗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春冲秽,著一層夾襖步出監(jiān)牢的瞬間舍咖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工锉桑, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留排霉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓民轴,卻偏偏與公主長得像攻柠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子后裸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容