2013-03-13 34 views
4

因此,有人找到一個項目,該項目與對象(使用Visual Studio 2010)中已經定義的錯誤LNK2005:符號鏈接失敗。在這種情況下,我知道什麼是錯誤的(因此可以指出他們正確的解決方案),但我不知道爲什麼這是錯誤的水平給予一個很好的解釋(以防止它發生再次)。我該如何解釋這個LNK2005?

// something.h 
#ifndef _SOMETHING_H 
#define _SOMETHING_H 
int myCoolFunction(); 

int myAwesomeFunction() // Note implementing function in header 
{ 
    return 3; 
} 
#endif 

-

// something.cpp 
#include "something.h" 
int myCoolFunction() 
{ 
    return 4; 
} 

-

// main.cpp 
#include <iostream> 
#include "something.h" 

int main() 
{ 
    std::cout << myAwesomeFunction() << std::endl; 
} 

此失敗連接,並且通過將myAwesomeFunction()插入的.cpp和留在.H聲明固定。

我對鏈接器工作原理的理解來自here。就我的理解,我們提供了一個在一個地方需要的符號。

我擡起頭看MSDN article on LNK2005,它符合我期望鏈接器行爲的方式(不止一次提供符號 - >鏈接器被混淆),但似乎沒有涵蓋這種情況(這意味着我不理解某些東西關於鏈接顯而易見)。

谷歌和StackOverflow上的人不包括成品率問題的#ifndef#pragma once(這會導致提供的符號的多個聲明)

A related question I found on this site有同樣的問題,但得到的答覆並沒有解釋爲什麼我們是充分理解這個問題。

我有一個問題,我知道解決的辦法,但我不知道爲什麼我的解決方案的工作

+0

如果通過預處理器運行所有的.cpp文件,那麼計算'myAwesomeFunction()'有多少個全局名稱空間實例可能會更有意義。你知道問題出在哪裏,未來避免這個「問題」的解決方案是微不足道的「醫生,當我做這個*時它會受到傷害......」「那麼不要這樣做。」**。 – WhozCraig 2013-03-13 12:21:07

+0

我很想知道我爲什麼要做點什麼,所以如果有人說「嘿,你爲什麼這麼做?」我比「哦,有人告訴我」有更好的迴應。 但是,在日常生活中,不會濫用鏈接器通常是我嘗試和避免的。 – KidneyChris 2013-03-13 12:32:38

+0

你我都。編譯器是開放遊戲,但我試圖尊重鏈接器= P畢竟,它是第九局中更接近的。 – WhozCraig 2013-03-13 12:34:28

回答

3

在一個典型的C++項目中,您分別編譯每個實現(或.cpp)文件 - 您通常不會直接將頭文件(或.h)文件傳遞給編譯器。在所有預處理和內含物被執行之後,這些文件中的每一個都變成翻譯單元。所以,在這個例子中,你已經給了,有兩個翻譯單元看起來像這樣:

  • main.cpp翻譯單元:

    // Contents of <iostream> header here 
    
    int myCoolFunction(); 
    
    int myAwesomeFunction() // Note implementing function in header 
    { 
        return 3; 
    } 
    
    int main() 
    { 
        std::cout << myAwesomeFunction() << std::endl; 
    } 
    
  • something.cpp翻譯單元:

    int myCoolFunction(); 
    
    int myAwesomeFunction() // Note implementing function in header 
    { 
        return 3; 
    } 
    
    int myCoolFunction() 
    { 
        return 4; 
    } 
    

請注意,這兩個翻譯單元都包含重複的內容他們都包括something.h。如您所見,上述翻譯單元中只有一個包含myCoolFunction的定義。那很好!然而,它們均爲包含myAwesomeFunction的定義。那很糟!

翻譯單元分開編譯後,它們被鏈接起來形成最終的程序。關於跨翻譯單位的多個聲明有一定的規則。其中一個規則是(§3.2/ 4):

每個程序都應該包含每個非內聯函數或該程序中odr使用的變量的一個定義;不需要診斷。

您在程序中有多個定義myAwesomeFunction,因此您違反了規則。這就是爲什麼你的代碼沒有正確鏈接。

你可以從鏈接器的角度來思考它。在編譯這兩個翻譯單元之後,您有兩個目標文件。鏈接器的工作是將目標文件連接在一起以形成最終的可執行文件。所以它看到mainmyAwesomeFunction的調用,並試圖在一個目標文件中找到相應的函數定義。但是,還有兩個的定義。鏈接器不知道使用哪一個,所以它只是放棄。

現在讓我們看看翻譯單位是什麼樣子,如果你定義myAwesomeFunctionsomething.cpp

  • 固定main.cpp翻譯單元:

    // Contents of <iostream> header here 
    
    int myCoolFunction(); 
    
    int myAwesomeFunction(); 
    
    int main() 
    { 
        std::cout << myAwesomeFunction() << std::endl; 
    } 
    
  • 固定something.cpp翻譯單元:

    int myCoolFunction(); 
    
    int myAwesomeFunction(); 
    
    int myCoolFunction() 
    { 
        return 4; 
    } 
    
    int myAwesomeFunction() 
    { 
        return 3; 
    } 
    

現在它是完美的。現在整個程序中只有一個myAwesomeFunction的定義。當鏈接器在main中看到myAwesomeFunction的調用時,它知道正好是它應將其鏈接到哪個函數定義。

+0

因此,繼續我的愚蠢問題的趨勢:如果我的整個頭文件被轉儲到每個翻譯單元中,那麼包含守衛會做些什麼?他們如何工作? – KidneyChris 2013-03-13 12:30:01

+2

@KidneyChris包含防止標題被包含*在同一個翻譯單元中*兩次。標題*應該包含在多個翻譯單元中。 – 2013-03-13 12:31:46

+0

@stfrabbit Heh。我問了一個問題,「我不想知道它爲什麼會這樣做」,作爲其中的一部分,我認爲我在每個標題的頂部打了#ifndef。我的心智模式現在匯合在一起。我認爲在這個例子中,包括衛兵是完全不必要的,因爲沒有包括包含something.h的文件嗎? – KidneyChris 2013-03-13 12:37:19

1

鏈接器只是讓你知道,你打破了一個定義規則。這是一個基本的,有詳細記錄的C++規則 - 它不能通過使用include guard或#pragma once指令來解決,但在免費函數的情況下,通過將其標記爲inline或將實現移動到源文件。

當一個非內聯方法在一個頭中實現時,所有包含該頭的翻譯單元將會定義它。當相應的.obj文件鏈接在一起時,鏈接程序檢測到多次導出(和定義)相同的符號,並且抱怨。

移動執行到cpp文件有效將您的初始定義聲明

-2

myAwesomeFunction定義在兩個源文件中:something.cppmain.cpp。將其實現移至其中一個源文件,或將此函數聲明爲靜態。

+0

他知道那一部分。 – 2013-03-13 13:30:15