使用內聯函數的一個問題

最近碰到一個與內聯方法有關的編譯問題,記敘以下。html

問題背景

類Scheduler的實現以下所示,其中方法SetStates()僅僅被類自己使用(暫且先無論它的public屬性)。程序員

// scheduler.hpp
class Scheduler {

public:
  Scheduler():m_state1(0),m_state2(0) {}
  ~Scheduler() {}

  inline void SetStates(int state1, int state2);

  int GetState1() {
    return m_state1;
  }
  int GetState2() {
    return m_state2;
  }

private:
  int m_state1;
  int m_state2;
};
// scheduler.cpp
void Scheduler::SetStates(int state1, int state2) {
  m_state1 = state1;
  m_state2 = state2;
}

如上代碼構建正常。以後,新建一個新的類SchedulerMgmt,而且在其中使用了類Scheduler當中的SetStates()方法:app

// SchedulerMgmt.cpp
void SchedulerMgmt::UpdateSchedulerState(int state1, int state2) {
  m_scheduler.SetStates(state1, state2);
}

從新編譯,提示錯誤:函數

scheduler.hpp:10: warning: inline function ‘void Scheduler::SetStates(int, int)’ used but never defined
/tmp/ccsOhsNk.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)':
scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)'
collect2: ld returned 1 exit status

因爲編譯錯誤的產生僅僅是在新增長類SchedulerMgmt,並在其中調用了Scheduler中的SetStates()以後才產生,同時該方法剛好聲明爲內聯方法,很天然的便懷疑到這個方法的聲明之上:inline。因而前後嘗試了下面兩種方法來排錯:測試

  • A: 在scheduler.cpp當中爲SetScheduleParams方法的實現加上"inline"關鍵字。
  • B: 去掉scheduler.hpp當中SetScheduleParams方法的「inline」聲明。

測試的結果是第一種方法A無論用,編譯錯誤依然存在,方法B解決了問題。編碼

這樣的結果讓本身感到疑惑不已。衆所周知,將方法聲明爲內聯的方式有兩種,其一爲在class的定義當中定義成員方法;其二是使用inline關鍵字。第一種方式通過測試是可行的,然而爲何第二種方式並無達到目的呢?難道問題出在外部調用內聯方法上面?這是第一個疑惑。code

方法B在將SetStates()的內聯屬性去掉問題消失,這是不難理解的。但當我僅僅在其定義上加上inline修飾符,便又會出現問題,只是這個時候僅僅剩下連接的問題:htm

/tmp/ccoizmZA.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)':
scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)'
collect2: ld returned 1 exit status

這是另一個疑惑。看來對於內聯的許多細節,本身以前並未注意到。blog

內聯方法的基本要求

在查閱了C++標準最新的Draft以後,從中找到了針對「方法A沒有解決編譯錯誤」的解釋:get

An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case (3.2). [ Note: A call to the inline function may be encountered before its definition appears in the translation unit. —end note ].

這裏提到內聯函數的基本要求:任一調用該函數的地方均須要看到它的定義(這一點上,C++中的template也有這樣的要求)。因此在每個編譯單元都須要定義(注:在程序的構建工程當中,編譯階段會對每個源代碼文件分別編譯、彙編,以後將以後的輸出文件進行連接等處理,這裏的編譯單元能夠看作是一個個獨立的源代碼文件。)可見,將內聯成員方法定義在類的定義裏面是最爲穩妥的。在方法A當中,類SchedulerMgmt中儘管能夠看到SetStates()聲明爲inline,可是卻沒法見着它的定義,便提示錯誤。這便是如上第一個疑惑的解答案。

編譯器對於內聯方法處理上的差別

對於第二個疑惑,其實能夠歸結於一點:編譯階段對於類中的普通成員方法與內聯成員方法的處理有差別,但差別在什麼地方?要回答這個問題,能夠從編譯以後的結果上去看。

使用objdump -s -d scheduler.o分別查看輸出修改先後的scheduler.cpp編譯以後的彙編碼:

可見,在將SetStates()定義爲內聯方法時,在編譯以後的目標文件中並不會包含該函數的指令,也就是說編譯器將一個方法內聯以後,並不會在目標文件當中將其保留,就如同宏在預處理階段直接文本擴展同樣。所以,到這裏第二個疑惑解開。

上面是筆記的主要內容。其實在參考資料<sup>2</sup>連接的一篇文章中介紹了有關extern inline的知識,權因本筆記是對工做中問題的一次小總結,因此再也不將其歸入討論。

編譯器:

g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48) Copyright (C) 2006 Free Software Foundation, Inc.

參考:
  1. << Working Draft, Standard C++, n4296.pdf >>
  2. http://www.cnblogs.com/cnmaizi/archive/2011/01/19/1939686.html
  3. << 程序員的自我修養—連接、裝載與庫 >>
相關文章
相關標籤/搜索