1. 程式人生 > >關於 inline 函式的分析: *** undefined reference to ***

關於 inline 函式的分析: *** undefined reference to ***

  1. 如果將函式的實現放在標頭檔案中,那麼每一個包含該標頭檔案的cpp檔案都將得到一份關於該函式的定義,那麼連結器會報函式重定義錯誤。
  2. 如果將函式的實現放在標頭檔案,並且標記為 inline 那麼每一個包含該標頭檔案的cpp檔案都將得到一份關於該函式的定義,並且連結器不會報錯
  3. 如果將函式的實現放在cpp檔案中,並且沒有標記為inline,那麼該函式可以被連線到其他編譯單元中。
  4. 如果將函式的實現放在cpp文件中,並且標記為inline, 那麼該函式對其他編譯單元不可見(類似static的效果),也就是其他cpp檔案不能連結該函式庫,這就是標題中出現的 … undefined reference to …
    問題原因就是,編譯器在編譯一個inline函式時,需要知道其完整定義,如果編譯器在本編譯單元找不到inline函式定義就會報錯(inline
    函式的呼叫不涉及到函式的call,也就不需要連結器參與進來工作,所以也就不會去其他編譯單元查詢函式定義)。

所以,將類的成員函式的實現放在標頭檔案中不會出現重定義錯誤,是因為在類中定義成員函式預設為inline函式。

所以下面會報重定義錯誤(當頭檔案被兩個及以上的cpp檔案包含時):

# foo.hpp
class Foo
{
    Foo();
};
Foo::Foo()  // 實現在標頭檔案中,並且沒標記為inline,會報編譯錯誤!
{
}

但是如下這樣就不會報錯:

# foo.hpp
class Foo
{
    Foo()  // 預設 inline
    {}
};

或者如下這樣也不會報錯:

# foo.hpp
class Foo
{
    inline Foo();
};
Foo::Foo()  // 實現在標頭檔案中,並且顯示標記為inline
{
}

如果debug的時候,發現自己明明定義了該函式,卻還是報 *** undefined reference to *** 錯誤,那麼可以看一下是不是不小心把cpp檔案中的函式寫成inline了。

不過,將 inline 函式定義在 cpp 檔案中有個好處,正如 《大規模c++程式設計》一書中所說,那就是他擁有 internal linkage 屬性(不會輸出符號到.o檔案中),不會汙染全域性名稱空間(類似static函式):

// file1.cpp
int i                                          // external linkage
int max(int a, int b) {return a > b ? a : b;}  // external linkage
// file2.cpp
inline int min(int a, int b) {return a < b ? a : b;}       // internal
static int fact(int n) {return n<-1 ? 1 : n * fact(n-1);}  // internal

其他同樣不會輸出符號到.o檔案中的有:

// file3.cpp
class Link;  // 類宣告
enum { START_SIZE = 1, GROW_FACTOR = 2};  // 列舉定義
const double PI = 3.1415926595;
static Link *s_root_p;
Link * const s_first_p = s_root_p;
typedef int (PointerToFunc *) ();
#define CASE(x) case X: cout << "x" << endl;