最近碰到一個與內聯方法有關的編譯問題,記敘以下。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無論用,編譯錯誤依然存在,方法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.