GCC和C99 中的inline

內聯函數是代碼被插入到調用者代碼處的函數。如同 #define 宏(但並不等同,原因見下文),內聯函數通過避免被調用的開銷來提高執行效率,尤其是它能夠通過調用(“過程化集成”)被編譯器優化。

    gcc對C語言的inline做了自己的擴展,其行爲與C99標準中的inline有較大的不同。

1.1. static inline
    GCC的static inline定義很容易理解:你可以把它認爲是一個static的函數,加上了inline的屬性。這個函數大部分表現和普通的static函數一樣,只不過在調用這種函數的時候,gcc會在其調用處將其彙編碼展開編譯而不爲這個函數生成獨立的彙編碼。除了以下幾種情況外:
    函數的地址被使用的時候。如通過函數指針對函數進行了間接調用。這種情況下就不得不爲static inline函數生成獨立的彙編碼,否則它沒有自己的地址。
    其他一些無法展開的情況,比如函數本身有遞歸調用自身的行爲等。
    static inline函數和static函數一樣,其定義的範圍是local的,即可以在程序內有多個同名的定義(只要不位於同一個文件內即可)。
    注意:gcc的static inline的表現行爲和C99標準的static inline是一致的。所以這種定義可以放心使用而沒有兼容性問題。
    要點:gcc的static inline相對於static函數來說只是在調用時建議編譯器進行內聯展開;gcc不會特意爲static inline函數生成獨立的彙編碼,除非出現了必鬚生成不可的情況(如通過函數指針調用和遞歸調用)
gcc的static inline函數僅能作用於文件範圍內
1.2. inline
    相對於C99的inline來說,GCC的inline更容易理解:可以認爲它是一個普通全局函數加上了inline的屬性。即在其定義所在文件內,它的表現和static inline一致:在能展開的時候會被內聯展開編譯。但是爲了能夠在文件外調用它,gcc一定會爲它生成一份獨立的彙編碼,以便在外部進行調用。即從文件外部看來,它和一個普通的extern的函數無異。舉個例子:
        foo.c:
       
        inline foo() {
                          ...;   <- 編譯器會像非inline函數一樣爲foo()生成獨立的彙編碼
        }
        void func1() {
                         foo(); <- 同文件內foo()可能被編譯器內聯展開編譯而不是直接call上面生成的彙編碼
        }
    而在另一個文件裏調用foo()的時候,則直接call的是上面文件內生成的彙編碼:
        bar.c:
            extern foo(); <- 聲明foo(),注意不能在聲明內帶inline關鍵字
            void func2() {
                             foo();    <- 這裏就是直接call在foo.c內爲foo()函數生成的彙編碼了
                          } 
    雖然gcc的inline函數的行爲很好理解,但是它和C99的inline是有很大差別的。

    要點: 
    gcc的inline函數相對於普通extern函數來說只是在同一個文件內調用時建議編譯器進行內聯展開; 
    gcc一定會爲inline函數生成一份獨立的彙編碼,以便其在本文件之外被調用。在別的文件內看來,這個inline函數和普通的extern函數無異;
    c的inline函數是全局性的:在文件內可以作爲一個內聯函數被內聯展開,而在文件外可以調用它

1.3. extern inline
    GCC的static inline和inline都很好理解:看起來都像是對普通函數添加了可內聯的屬性。但是這個extern inline就千萬不能想當然地理解成就是一個extern的函數+inline屬性了。實際上gcc的extern inline十分古怪:一個extern inline的函數只會被內聯進去,而絕對不會生成獨立的彙編碼!即使是通過指針應用或者是遞歸調用也不會讓編譯器爲它生成彙編碼,在這種時候對此函數的調用會被處理成一個外部引用。另外,extern inline的函數允許和外部函數重名,即在存在一個外部定義的全局庫函數的情況下,再定義一個同名的extern inline函數也是合法的。以下用例子具體說明一下extern inline的特點:
        foo.c:
        extern inline
        int foo(int a)
        {
            return (-a);
        }
        void func1()
       {
           ...;
           a = foo(a);   ①
           p_foo = foo;  ②
           b = p_foo(b); ③
        }
    在這個文件內,gcc不會生成foo函數的彙編碼。在func1中的調用點①,編譯器會將上面定義的foo函數在這裏內聯展開編譯,其表現類似於普通inline函數。因爲這樣的調用是能夠進行內聯處理的。而在②處,引用了foo函數的地址。但是注意:編譯器是絕對不會爲extern inline函數生成獨立彙編碼的!所以在這種非要個函數地址不可的情況下,編譯器不得不將其處理爲外部引用,在鏈接的時候鏈接到外部的foo函數去(填寫外部函數的地址)。這時如果外部沒有再定義全局的foo函數的話就會在鏈接時產生foo函數未定義的錯誤。
    假設在另一個文件裏面也定義了一個全局函數foo:
        foo2.c:
        int foo(int a)
        {
            return (a);
        }
    那麼在上面那個例子裏面,後面一個對foo函數地址的引用就會在鏈接時被指到這個foo2.c中定義的foo函數去。也就是說:①調用foo函數的結果是a=-a,因爲其內聯了foo.c內的foo函數;而③調用的結果則是b=b,因爲其實際上調用的是foo2.c裏面的foo函數!
    extern inline的用法很奇怪也很少見,但是還是有其實用價值的。第一:它可以表現得像宏一樣,可以在文件內用extern inline版本的定義取代外部定義的庫函數(前提是文件內對其的調用不能出現無法內聯的情況);第二:它可以讓一個庫函數在能夠被內聯的時候儘量被內聯使用。舉個例子:
    在一個庫函數的c文件內,定義一個普通版本的庫函數libfunc:
        lib.c:
        void libfunc()
        {
            ...;
        }
    然後再在其頭文件內,定義(注意不是聲明!)一個實現相同的exterin inline的版本:
        lib.h:
        extern inline libfunc()
       {
            ...;
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章