[轉]文件包含 頭文件 inline 編譯

inline 函數遇到的問題。inline 在頭文件中。不會編譯?   

C語言中的函數或變量有且只有一個定義,但它可以有多個extern聲明。唯一性
    規則也適用於常數定義,結構定義,類型定義,宏定義,只不過它們默認只對本
    單元可見;而函數和變量(函數外)的定義默認是全局可見的。

Linux0.11的目錄:
      |-boot/
      |-fs/
      |-include/
        |-asm/
        |-linux/
        |-sys/
      |-init/
      |-kernel/
        |-blk_drv/
        |-chr_drv/
        |-math/ 
      |-lib/
      |-mm/
      |-tools/
      |-Makefile

頭文件(包括)
    常數定義 
    宏定義 
    類型定義
    數據結構 
    變量聲明 
    函數聲明(extern inline函數算在內)
    
    頭文件中包含的一切內容都只是對本編譯單元可見的。如果要使用,就include
    此頭文件,也可以不include而採用另外的定義。
    
    1.變量和函數使用之前都要聲明。另外如果該函數只在本單元內使用,可以不用
      聲明,但定義要在調用之前。如kerner/signal.c中的函數save_old定義在前,
      然後只在sys_sigaction中調用,而且save_old有限定詞static。頭文件一般
      包含“常數定義,宏定義,數據結構,函數聲明,變量聲明”。變量定義是不可
      能包含在頭文件中的,因爲無論函數或者變量只能定義一次。
    2.#define宏定義的作用域在源文件之內,既可以在頭文件中定義,也可以在.c文件
      中定義。比如set_bit在fs/bitmap.c和fs/super.c中都有定義,而且不同,當然
      只各自在本文件中調用。
    3.結構也可以在.c文件中定義,lib/malloc.c裏面就定義了一些結構供自己使用。
      在linux內核中,爲了避免多一些不必要的頭文件,比如fs/char_dev.c中就直接
      聲明自己要調用的函數,所以函數聲明也可以寫在.c文件中。
    4.extern在函數聲明中表示其是一個聲明,而不是定義;在函數定義中表示函數是
      全局的。不過這兩個地方的extern都可以省略,函數聲明可以省略是因爲聲明沒
      有函數體,而定義必須有函數體;函數定義可以省略是因爲缺省就是全局可見的。
      其實這兩個extern的含義不同。linus在有些函數聲明前加了extern,有些沒有,
      應該只是習慣性的,沒有本質的區別。
    5.string.h頭文件中包含了一些以inline內嵌函數的形式定義的字符串操作函數。
      下面inline中具體說明。不過函數定義是不能包含在頭文件中的,否則會造成
      重複定義的錯誤。
    6.頭文件中有些聲明但沒有定義的函數,是在庫中定義的,當然這些庫是不包含在
      內核代碼中的。比如unistd.h中的系統調用都是在庫裏面封裝的。再如sys/stat.h
      中chmod函數其實在unistd.h中也有聲明,我想這是linus爲了方便include頭文件
      才又聲明瞭一遍的,而且這兩處的定義是一致的。
      
inline
Inlining of functions is an optimization and it really “works” only in 
optimizing compilation. If you don't use -O, no function is really inline.
    一個函數的inlining是一種優化,而它只會"work"在有優化選項的編譯中。如果你
沒有(在GCC中)用到-O選項,任何函數都不會被inline。
When a function is both inline and static, if all calls to the function are 
integrated into the caller, and the function's address is never used, then
the function's own assembler code is never referenced. In this case, GCC 
does not actually output assembler code for the function, unless you specify
the option -fkeep-inline-functions. Some calls cannot be integrated for 
various reasons (in particular, calls that precede the function's definition 
cannot be integrated, and neither can recursive calls within the definition). 
If there is a nonintegrated call, then the function is compiled to assembler 
code as usual. The function must also be compiled as usual if the program 
refers to its address,because that can't be inlined. 
    當一個函數同時是inline和static的,如果對這個函數的所有調用都可以直接在調用
函數中展開,而且此函數的地址沒有被引用(我的理解是函數指針調用),即這個函數的
彙編代碼沒有被引用。這種情況下,GCC實際上不生成此函數的彙編代碼,除非你指定
-fkeep-inline-functions選項。某些條件下有些函數調用不能直接展開(比如,在函數
定義之前的函數調用,再如函數定義中有遞歸)。如果有沒有被直接展開的函數調用,此
函數會按例生成彙編代碼。當程序引用了此函數的地址時,這個函數也不能直接在調用中
展開,因此也按例被編譯。
When an inline function is not static, then the compiler must assume that there 
may be calls from other source files; since a global symbol can be defined only 
once in any program, the function must not be defined in the other source files, 
so the calls therein cannot be integrated. Therefore, a non-static inline 
function is always compiled on its own in the usual fashion.
    當一個inline函數不是static,編譯器會假定它會被其它編譯單元調用,因爲一個全局
符號在一個程序中只能定義一次,所以這個函數不會在其他編譯單元中被定義,所以那裏的
函數調用就不能被直接展開。因此,非static的inline函數都會按例被編譯。
If you specify both inline and extern in the function definition, then the 
definition is used only for inlining. In no case is the function compiled on its 
own, not even if you refer to its address explicitly. Such an address becomes an 
external reference, as if you had only declared the function, and had not 
defined it. 
    當一個函數是inline和extern的,這個函數定義只會在調用中展開,而不會被編譯,即使
你明確引用它的地址。這個地址會成爲一個外部引用,就好像你只是聲明瞭這個函數,卻沒有
定義它。
This combination of inline and extern has almost the effect of a macro. The way to 
use it is to put a function definition in a header file with these keywords, and 
put another copy of the definition (lacking inline and extern) in a library file. 
The definition in the header file will cause most calls to the function to be 
inlined. If any uses of the function remain, they will refer to the single copy 
in the library. 
    inline和extern的函數基本上和一個宏定義的作用相當。使用的方法是在頭文件中用這
兩個關鍵字定義,而把另外一份相同定義卻沒有inline和extern關鍵字的copy放在庫中。頭文
件中的定義會使大部分的函數調用直接展開。如果還存在(通常開銷)的函數調用,它就會使
用庫中的定義。

    上面是GCC的說明文檔,隨GCC免費發佈。歸結起來有以下幾點:
    1.inline是一個編譯優化選項,需要滿足一定的條件纔會達到優化的目的,即直接在調用
      時展開,而省去調用函數的開銷。
    2.static inline表明這個函數只在本編譯單元中可見。inline或者extern inline都意味
      着此函數是全局可見的;不同的是inline限定的函數在定義的編譯單元中被直接展開,
      而在其它調用關係的編譯單元中被直接函數調用,而extern inline限定的函數在所有的
      調用中都被直接展開,相當於宏展開,強行的函數調用無效,除非定義一個非extern 
      inline限定的版本放在庫中以供調用,所以這個extern表明這只是個聲明。
    
    在linux0.11中有兩種使用方法:
    1.init/main.c中定義fork,pause等函數用了static inline,因爲只在本單元中使用,限制
      只在本單元中可見。
    2.include/string.h中的字符串操作函數都用了extern inline,而在使用時包含string.h頭
      文件。這裏的定義也相當於聲明,並沒有在其它地方又聲明,也不會因爲多個文件中包含
      string.h頭文件而引起重複定義的錯誤。我試了一下,在linux0.11中將string.h中的
      函數strcpy前的限定詞extern去掉的話,編譯會引起重複定義的錯誤(注意修改string.h
      頭文件後要進入與string.h有依賴關係的目錄執行make,如fs/,因爲最外層的makefile並
      不依賴於string.h,會提示nothing can do)。
      
條件編譯
    1.#ifndef
      #else           /******可選******/
      #endif
      
      (1)頭文件中包含一些常用的常數定義時,爲防止包含多個頭文件時重複定義,採用條件編譯
         即沒有定義時才定義
         #ifndef NULL
         #define NULL ( (void *)0)
         #endif
      (2)最常用的爲防止包含多個頭文件時,把一個頭文件包含了兩次,造成重複定義
         #ifndef _FILENAME_H
         #define _FILENAME_H
         #endif /* _FILENAME_H */
         
    2.#ifdef
      #else           /******可選******/
      #endif
     
      (1)決定程序的某一部分參加編譯與否
         #ifdef __LIBRARY__
         #endif   /****__LIBRARY__****/
         需要這一部分代碼的之前#define __LIBRARY__
         
    3.#if defined
      #elif defined
      #else
      #endif
      
      單操作數運算符defined可以使用在#if或#elif僞指令中. 
      #if  defined  _SYMBOL_ 等效於#ifdef   _SYMBOL_
      #if !defined  _SYMBOL_ 等效於#ifndef  _SYMBOL
         
      (1)有多項選擇的時候使用很方便
         #if defined(vax) || defined(hp300) || defined(pyr)
         #define SEGMENT_SIZE PAGE_SIZE
         #endif
         
      (2)判斷表達式
         #ifdef MAJOR_NR
           #if (MAJOR_NR == 1)
           #elif (MAJOR_NR == 2)
           #elif (MAJOR_NR == 3)
           #elif
         #elif
         
      (3)可以註釋掉大段的代碼,/*...*/不能註釋掉本身包含*/的代碼,因爲/**/不能嵌套
         #if 0
         #endif

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