2015-12-05 37 views
3

在昂納霧的Optimizing C++ manual他有一個部分「內聯函數有一個非內聯拷貝」,他寫道內聯函數有一個非內聯拷貝

函數內聯具有複雜性,同樣的功能可以被稱爲來自另一個模塊。編譯器必須製作內聯函數的非內聯副本,以便從另一個模塊調用該函數。如果沒有其他模塊調用該函數,則此非內聯副本是死代碼。代碼的碎片使得緩存效率降低。

讓我們來測試一下。

foo.h中

inline double foo(double x) { 
    return x; 
} 

t1.cpp

#include "foo.h" 
double t1(double x) { 
    return foo(x); 
} 

的main.cpp

#include <stdio.h> 
extern double foo(double); 

int main(void) { 
    printf("%f\n", foo(3.14159)); 
} 

編譯g++ t1.cpp main.cpp並且它運行正常。如果我做g++ -S t1.cpp main.cpp並查看程序集,我看到main.s調用t1.s中定義的函數。做g++ -c main.cppg++ t1.cpp並看着nm的符號顯示U _Z3foodmain.oW _Z3foodt1.o。所以很明顯,Agner聲稱存在非內聯副本是正確的。

怎麼辦g++ -O1 t1.cpp main.cpp由於foo未定義,因此無法編譯。g++ -O1 t1.cppnm t1.o顯示_Z3food已被刪除。

現在我很困惑。我不希望g ++在啓用優化的情況下刪除非內聯副本。

看來,啓用優化inline相當於static inline。但沒有優化inline意味着有一個非內聯副本生成。

也許海灣合作委員會不認爲我會永遠想要的非內聯副本。但我可以想到一個案例。假設我想創建一個庫,並且在庫中我想要一個在多個翻譯單元中定義的函數(以便編譯器可以在每個翻譯單元中內聯該函數的代碼),但是我還想要一個連接到我的庫的外部模塊能夠調用庫中定義的函數。我顯然需要一個非內聯版本的功能。

一個建議Agner給出瞭如果我不想讓非內聯副本使用static inline。但是從這個question and answers我推斷這隻對顯示意圖有用。所以一方面很明顯,它不僅僅意圖不使用優化,因爲它使非內聯副本成爲可能。但另一方面,通過優化,它似乎只是顯示了意圖,因爲非內聯副本被剝離了。這很混亂。

我的問題:

  1. 是gcc正確的剝離了與優化非內嵌拷貝啓用?換句話說,如果我不使用static inline,應該總是有非內聯副本?
  2. 如果我想確定沒有非內聯副本,我應該使用static inline

我才意識到,我可能誤解昂納的聲明。當他說函數inlinng時,他可能會引用編譯器傾斜代碼而不是使用關鍵字inline。換句話說,他可能指的是用extern定義的功能,而不是inlinestatic

例如

//foo.cpp 
int foo(int x) { 
    return x; 
} 

float bar(int x) { 
    return 1.0*foo(x); 
} 

//main.cpp 
#include <stdio.h>  
extern float bar(int x);  
int main(void) { 
    printf("%f\n", bar(3)); 
} 

編譯gcc -O3 foo.cpp main.cpp表明foo是內嵌在bar但其從未使用過的foo非內聯拷貝是二進制。

+0

我不知道這個標準是什麼意思,但它對於常見的情況是有意義的。通常inline/constexpr函數是在頭文件中定義的,所以它被聲明但未被定義的情況不會發生。 – kamikaze

+0

@ kamikaze,你能更具體一些嗎?你在「它是有道理的」中指的是什麼......我不知道你的意思是什麼,但是沒有定義。我很抱歉我的問題太長了。 –

+1

我認爲這一點是,你應該能夠創建一個指向內聯函數的指針(它將指向非內聯版本),並且具有該非內聯版本的單個實例會導致生成的代碼更少,以防多個翻譯單元需要有一個指向「內聯函數」的指針。 – PSkocik

回答

6

的標準說的inline方法的完整定義需要在使用每一個翻譯單元可見它:

內聯函數應在每一個轉換單元被限定在其中是ODR使用的並且在每種情況下都應該具有相同的定義(3.2)。 [...]如果具有外部鏈接的功能是在一個翻譯單元中內聯申報的 ,則應在其出現的所有翻譯單元內聲明爲內聯; 不需要診斷。

(在N4140 7.1.2/4)

這確實讓病態的在你的問題的例子。

此規則還包括來自任何外部模塊鏈接庫的每個TU。他們還需要C++代碼中的完整定義,例如通過在標題中定義函數。因此,如果當前翻譯不需要它,編譯器可以安全地省略任何種類的「非內聯副本」

關於確定副本不存在:標準不保證任何優化,因此取決於編譯器。無論是否有附加static關鍵字。

+0

也許我沒有提出正確的問題。這是否意味着我的代碼錯誤?如果我沒有在'main.cpp'中定義內聯函數,我是不是應該從'main.cpp'調用'foo'。這就是我閱讀你的答案的方式。我的代碼示例必須是錯誤的。 –

+1

@Zboson是的,這確實是錯的。我添加了一個標準報價。 –

+0

Agner寫道:「如果沒有其他模塊調用該函數,則此非內聯副本是死代碼。」但爲什麼其他模塊會調用這個函數,除非它在同一個翻譯單元中定義? –