[英]Undefined reference to static const int
我今天遇到了一個有趣的問題。 考慮這個簡單的例子:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
編譯時出現錯誤:
Undefined reference to 'Bar::kConst'
現在,我很確定這是因為static const int
沒有在任何地方定義,這是有意為之的,因為根據我的理解,編譯器應該能夠在編譯時進行替換而不需要定義。 但是,由於 function 采用const int &
參數,它似乎沒有進行替換,而是更喜歡引用。 我可以通過進行以下更改來解決此問題:
foo(static_cast<int>(kConst));
我相信這現在迫使編譯器創建一個臨時 int,然后傳遞一個引用到它,它可以在編譯時成功地做到這一點。
我想知道這是否是故意的,還是我對 gcc 的期望過高而無法處理這種情況? 或者這是我出於某種原因不應該做的事情?
這是故意的,9.4.2/4 說:
如果靜態數據成員是 const 整型或 const 枚舉類型,則其在類定義中的聲明可以指定一個常量初始化器,它應該是整型常量表達式 (5.19) 在這種情況下,該成員可以出現在整型常量表達式中。 如果在程序中使用該成員仍應在命名空間范圍內定義
當您通過 const 引用傳遞靜態數據成員時,您“使用”它,3.2/2:
表達式可能會被求值,除非它出現在需要整型常量表達式的地方(見 5.19),是 sizeof 運算符(5.3.3)的操作數,或者是 typeid 運算符的操作數,並且表達式沒有指定左值多態類類型 (5.2.8)。 如果對象或非重載函數的名稱出現在潛在求值表達式中,則使用該對象或非重載函數。
因此,實際上,當您也按值傳遞或在static_cast
傳遞它時,您“使用”了它。 只是 GCC 在一種情況下讓您擺脫困境,而在另一種情況下卻沒有。
[編輯:gcc 正在應用 C++0x 草案中的規則:“名稱顯示為潛在求值表達式的變量或非重載函數是 odr 使用的,除非它是滿足出現在常量中的要求的對象表達式 (5.19) 和左值到右值的轉換 (4.1) 會立即應用。”。 靜態強制轉換立即執行左值-右值轉換,因此在 C++0x 中它不被“使用”。]
const 引用的實際問題是foo
有權獲取其參數的地址,並將其與例如存儲在全局中的另一個調用的參數地址進行比較。 由於靜態數據成員是唯一的對象,這意味着如果您從兩個不同的 TU 調用foo(kConst)
,則在每種情況下傳遞的對象的地址必須相同。 AFAIK GCC 無法安排,除非對象是在一個(並且只有一個)TU 中定義的。
好的,所以在這種情況下foo
是一個模板,因此定義在所有 TU 中都是可見的,所以也許編譯器理論上可以排除它對地址執行任何操作的風險。 但總的來說,您當然不應該獲取不存在對象的地址或引用;-)
如果您在類聲明中使用初始化程序編寫靜態常量變量,就好像您已經編寫了一樣
class Bar
{
enum { kConst = 1 };
}
並且 GCC 會以同樣的方式對待它,這意味着它沒有地址。
正確的代碼應該是
class Bar
{
static const int kConst;
}
const int Bar::kConst = 1;
這是一個非常有效的案例。 特別是因為foo可能是來自 STL 的函數,如std::count ,它將const T&作為其第三個參數。
我花了很多時間試圖理解為什么鏈接器在使用這樣一個基本代碼時會出現問題。
錯誤信息
對“Bar::kConst”的未定義引用
告訴我們鏈接器找不到符號。
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst
我們可以從 'U' 中看到 Bar::kConst 是未定義的。 因此,當鏈接器試圖完成它的工作時,它必須找到符號。 但是你只聲明了kConst 而沒有定義它。
C++中的解決方案也是如下定義:
template <typename T>
void foo(const T & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
const int Bar::kConst; // Definition <--FIX
int main()
{
Bar b;
b.func();
}
然后,您可以看到編譯器會將定義放入生成的目標文件中:
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst
現在,您可以看到“R”表示它是在數據部分中定義的。
您還可以將其替換為 constexpr 成員函數:
class Bar
{
static constexpr int kConst() { return 1; };
};
我認為 C++ 的這個人工制品意味着任何時候Bar::kConst
被引用,它的文字值都會被使用。
這意味着實際上沒有變量可供參考。
您可能必須這樣做:
void func()
{
int k = kConst;
foo(k);
}
g++ 4.3.4 版接受此代碼(請參閱此鏈接)。 但是 g++ 4.4.0 版拒絕了它。
簡單的技巧:在kConst
傳遞給函數之前使用+
。 這將防止常量被引用,這樣代碼就不會生成對常量對象的鏈接器請求,而是繼續使用編譯器時常量值。
C++17 中的正確方法是簡單地創建變量 constexpr:
static constexpr int kConst = 1;
constexpr 靜態數據成員是隱式內聯的。
我遇到了與 Cloderic 提到的相同的問題(三元運算符中的靜態常量: r = s ? kConst1 : kConst2
),但它僅在關閉編譯器優化(-O0 而不是 -Os)后才抱怨。 發生在 gcc-none-eabi 4.8.5 上。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.