2011-09-29 20 views
2

在C++中是合法的將一個常量放在頭文件中,通常C方法是將extern聲明放在頭文件中,而定義只在一個編譯單元中,但在C++中,以前的技術會導致二進制文件增加,因爲鏈接時不會刪除符號(使用gnu ld和visual studio進行測試)。有沒有很好的方法來做這些事情?我只能想到的定義或C-方式,但以後可能會少給優化房間...C++常量符號膨脹鏈接文件


[email protected]:0:/tmp$ g++ -c b.cc 
[email protected]:0:/tmp$ g++ -c a.cc 
[email protected]:0:/tmp$ nm a.o | c++filt | grep COOK 
0000000000000000 r AI_LIKE_COOKIES 
[email protected]:0:/tmp$ nm b.o | c++filt | grep COOK 
0000000000000000 r AI_LIKE_COOKIES 
[email protected]:0:/tmp$ g++ -o a a.o b.o 
[email protected]:0:/tmp$ nm a | c++filt | grep COOK 
0000000000400610 r AI_LIKE_COOKIES 
0000000000400618 r AI_LIKE_COOKIES 



[email protected]:0:/tmp$ cat a.h 
#ifndef a_h 
#define a_h 

//const double A = 2.0; 
//extern const double AI_LIKE_COOKIES; 
const double AI_LIKE_COOKIES = 5.0; 

#endif 
[email protected]:0:/tmp$ cat a.cc 
#include "a.h" 
using namespace std; 

extern void f(); 

//const double AI_LIKE_COOKIES = 2.0; 

int main(int argc, char *argv[]) 
{ 
    f(); 
} 
[email protected]:0:/tmp$ cat b.cc 
#include "a.h" 

void f() 
{ 
} 
[email protected]:0:/tmp$ 
+2

如果使用最小優化('-O1'),const對象將在編譯階段消失。由於它們具有內部鏈接,編譯器可以選擇將它們丟棄,因爲它們不是必需的。 –

回答

2

有你有兩個真正的選擇。你可以用外部鏈接定義一個常量,或者不定義。通過內部鏈接,假設優化已打開,您將只在每個翻譯單元中使用實際使用常量的副本

內部連接:

// a.h 
const double AI_LIKE_COOKIES = 5.0; 

外部聯動:

// a.h 
extern const double AI_LIKE_COOKIES; 

// a.c 
const double AI_LIKE_COOKIES = 5.0; 

然而,你可能會問, 「怎麼樣內聯常量?」不幸的是,浮點操作數不能真正被內聯。每當在函數中使用浮點常量時,該值將作爲常量存儲在內存中。考慮兩個函數:

// In func1.c 
double func1(double x) { return x + 5.7; } 

// In func2.c 
double func2(double x) { return x * 5.7; } 

很可能,兩個文件都會在某處包含一個常量5.7,然後從內存中加載該常量。沒有真正的優化*。你得到的5.7兩個副本,就好像你已經做到了這一點:

extern const double CONSTANT_1, CONSTANT_2; 
const double CONSTANT_1 = 5.7; 
const double CONSTANT_2 = 5.7; 
double func1(double x) { return x + CONSTANT_1; } 
double func2(double x) { return x * CONSTANT_2; } 

* 注:在一些系統上,你會得到更小的代碼,如果你知道該常數將被鏈接到相同的二進制圖像而不是從圖書館加載。

建議:使用在頭文件的extern,並限定在一個轉換單元的常數。代碼可能不會太慢​​,並且禁止鏈接時優化,這是確保最終產品中只有一個副本結束的唯一好方法。

這聽起來像很多大驚小怪的了八個字節,但...

彙編:

這裏是一個函數:

double func(double x) 
{ 
    return x + 5.0; 
} 

這裏的彙編,在x86_64:

_Z4funcd: 
.LFB0: 
    .cfi_startproc 
    .cfi_personality 0x3,__gxx_personality_v0 
    addsd .LC0(%rip), %xmm0 
    ret 
    .cfi_endproc 
.LFE0: 
    .size _Z4funcd, .-_Z4funcd 
    .section  .rodata.cst8,"aM",@progbits,8 
    .align 8 
.LC0: 
    .long 0 
    .long 1075052544 

注意符號LC0,這是一個包含值爲5.0的常量。內聯沒有做任何事情,只是使符號不可見,因此它不會出現在nm中。你仍然可以在每個使用常量的翻譯單元中得到一個常量副本。

+0

它仍然會有內部連接增加二進制大小,但更糟的是現在可以修改。 –

+1

static是const變量的默認值 –

+0

@MarkB:這是一個錯誤,已更正。 –

0

const放在每個標題中隱式使其成爲內部鏈接,因此它在每個翻譯單元中都被複制。 「C方式」是我相信這種處理方式的正常方式。

您還可以定義「常量」瑣碎的內聯函數(見std::numeric_limits<T>::max()

0

這是合乎邏輯的行爲。如果您需要使您的模塊取決於外部名稱,請改爲extern。在大多數情況下,它不是必需的。

6

聲明爲const的對象並未明確聲明的extern在C++中具有內部鏈接。這意味着每個翻譯單元都會獲得它自己的對象副本。

然而,因爲他們有內在聯繫,因此不能從其他翻譯單位被命名,在編譯可以檢測對象本身不使用 - 對於基本const對象,這只是意味着,如果它的地址是永遠服用;它的值可以根據需要替換 - 並從目標文件中省略。

即使在-O1,gcc也會執行此優化。

$ g++ -O1 -c a.cc 
$ g++ -O1 -c b.cc 
$ g++ -o a a.o b.o 
$ nm a.o | c++filt | grep COOK 
$ nm b.o | c++filt | grep COOK 
$ nm a | c++filt | grep COOK 
$ 
+0

對於調試我們通常編譯-O0 ... – piotr

+0

@piotr:是的,大多數人也是如此。由於各種原因,「調試」配置對象文件將變得更大,未使用的常量的額外副本不可能是最大的因素。在調試配置中,您通常不需要關心二進制文件大小。 –

+0

我無法將兩者都標記爲已回答,但是我贊成您的回答。謝謝。 – piotr