造成LNK2005錯誤主要有以下幾種情況:
1
1.重複定義全域變數。可能存在兩種情況:
A、對於一些初學編程的程式員,有時候會以為需要使用全域變數的地方就可以使用定義申明一下。其實這是錯誤的,全域變數是針對整個工程的。正確的應該是在一個CPP檔案中定義如下:int g_Test;那麼在使用的CPP檔案中就應該使用:extern int g_Test即可,如果還是使用int g_Test,那麼就會產生LNK2005錯誤,一般錯誤錯誤資訊類似:AAA.obj error LNK2005 int book c?book@@3HA already
defined in BBB.obj。切記的就是不能給變數賦值否則還是會有LNK2005錯誤。
這裡需要的是“聲明”,不是“定義”!根據C++標準的規定,一個變數是聲明,必須同時滿足兩個條件,否則就是定義:
(1)聲明必須使用extern關鍵字;(2)不能給變數賦初值
所以,下面的是聲明:
extern int a;
下面的是定義
int a; int a = 0; extern int a =0;
B、對於那麼編程不是那麼嚴謹的程式員,總是在需要使用變數的檔案中隨意定義一個全域變數,並且對於變數名也不予考慮,這也往往容易造成變數名重複,而造成LNK2005錯誤。
2.標頭檔的包含重複。往往需要包含的標頭檔中含有變數、函數、類的定義,在其它使用的地方又不得不多次包含之,如果標頭檔中沒有相關的宏等防止重複的連結的措施,那麼就會產生LNK2005錯誤。解決辦法是在需要包含的標頭檔中做類似的處理:#ifndef MY_H_FILE //如果沒有定義這個宏
#define MY_H_FILE //定義這個宏
……. //標頭檔主體內容
…….
#endif
上面是使用宏來做的,也可以使用先行編譯來做,在標頭檔中加入:
#pragma once
//標頭檔主體
3.使用第三方的庫造成的。這種情況主要是C運行期函數庫和MFC的庫衝突造成的。具體的辦法就是將那個提示出錯的庫放到另外一個庫的前面。另外選擇不同的C函數庫,可能會引起這個錯誤。微軟和C有兩種C運行期函數庫,一種是普通的函數庫:LIBC.LIB,不支援多線程。另外一種是支援多線程的:msvcrt.lib。如果一個工程裡,這兩種函數庫混合使用,可能會引起這個錯誤,一般情況下它需要MFC的庫先於C運行期函數庫被連結,因此建議使用支援多線程的msvcrt.lib。所以在使用第三方的庫之前首先要知道它連結的是什麼庫,否則就可能造成LNK2005錯誤。如果不得不使用第三方的庫,可以嘗試按下面所說的方法修改,但不能保證一定能解決問題,前兩種方法是微軟提供的:
A、選擇VC菜單Project->Settings->Link->Catagory選擇Input,再在Ignore libraries 的Edit欄中填入你需要忽略的庫,如:Nafxcwd.lib;Libcmtd.lib。然後在Object/library Modules的Edit欄中填入正確的庫的順序,這裡需要你能確定什麼是正確的順序,呵呵,God bless you!
B、選擇VC菜單Project->Settings->Link頁,然後在Project Options的Edit欄中輸入/verbose:lib,這樣就可以在編譯連結程式過程中在輸出視窗看到連結的順序了。
C、選擇VC菜單Project->Settings->C/C++頁,Catagory選擇Code Generation後再在User Runtime libraray中選擇MultiThread DLL等其他庫,逐一嘗試。
關於編譯器的相關處理過程,參考:
http://www.donews.net/xzwenlan/archive/2004/12/23/211668.aspx
這就是我所遇到過的LNK2005錯誤的幾種情況,肯定還有其他的情況也可能造成這種錯誤,所以我不希望你在看完這篇文章以後,再遇到LNK2005錯誤時候,不動腦筋的想對號入座的排除錯誤。編程的過程就是一個思考的過程,所以還是多多開動你的頭腦,那樣收穫會更多!
附錄:
編譯器處理相關
一.前置處理器-編譯器-彙編器-連結器
前置處理器會處理相關的預先處理指令,一般是以"#"開頭的指令。如:#include "xx.h" #define等。
編譯器把對應的*.cpp翻譯成*.s檔案(組合語言)。
彙編器則處理*.s產生對應的*.o檔案(obj目標檔案)
最後連結器把所有的*.o檔案連結成一個可執行檔(?.exe)
1.組件:
首先要知道組件(可以暫且狹義地理解為一個類)一般分為標頭檔(我喜歡稱為介面,如:*.h)及實現檔案(如:*.cpp)。
一般標頭檔會是放一些用來作聲明的東東作為介面而存在的。
而實現檔案主要是實現的具體代碼。
2.編譯單個檔案:
記住IDE在bulid檔案時只編譯實現檔案(如*.cpp)來產生obj,在vc下你可以對某個?.cpp按下ctrl+f7單獨編譯它
產生對應一個?.obj檔案。在編譯?.cpp時IDE會在?.cpp中按順序處理用#include包括進來的標頭檔
(如果該標頭檔中又#include有檔案,同樣會按順序跟進處理各個標頭檔,如此遞迴。。)
3.內部連結與外部連結:
內、外連結是比較基礎的東東,但是也是新手最容易錯的地方,所以這裡有必要祥細討論一下。
內部連結產生的符號只在本地?.obj中可見,而外部連結的符號是所有*.obj之間可見的。
如:用inline的是內部連結,在檔案頭中直接聲明的變數、不帶inline的全域函數都是外部連結。
在檔案頭中類的內部聲明的函數(不帶函數體)是外部連結,而帶函數體一般會是內部連結(因為IDE會盡量把它作為內嵌函式)
認識內部連結與外部連結有什麼作用呢?下面用vc6舉個例子:
// 檔案main.cpp內容:
void main(){}
// 檔案t1.cpp內容:
#include "a.h"
void Test1(){ Foo(); }
// 檔案t2.cpp內容:
#include "a.h"
void Test2(){ Foo(); }
// 檔案a.h內容:
void Foo( ){ }
好,用vc產生一個空的console程式(File - new - projects - win32 console application),並關掉先行編譯選項開關
(project - setting - Cagegoryrecompiled Headers - Not using precompiled headers)
現在你開啟t1.cpp按ctrl+f7編譯產生t1.obj通過
開啟t2.cpp按ctrl+f7編譯產生t2.obj通過
而當你連結時會發現:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (?Foo@@YAXXZ) already defined in t1.obj
這是因為:
1. 編譯t1.cpp在處理到#include "a.h"中的Foo時看到的Foo函數原型定義是外部連結的,所以在t1.obj中記錄Foo符號是外部的。
2. 編譯t2.cpp在處理到#include "a.h"中的Foo時看到的Foo函數原型定義是外部連結的,所以在t2.obj中記錄Foo符號是外部的。
3. 最後在連結 t1.obj 及 t2.obj 時, vc發現有兩處地方(t1.obj和t2.obj中)定義了相同的外部符號(注意:是定義,外部符號可以
多處聲明但不可多處定義,因為外部符號是全域可見的,假設這時有t3.cpp聲明用到了這個符號就不知道應該調用t1.obj
中的還是t2.obj中的了),所以會報錯。
解決的辦法有幾種:
a.將a.h中的定義改寫為聲明,而用另一個檔案a.cpp來存放函數體。(提示:把上述程式改來試試)
(函數體放在其它任何一個cpp中如t1.cpp也可以,不過良好的習慣是用對應cpp檔案來存放)。
這時包括a.h的檔案除了a.obj中有函數體代碼外,
其它包括a.h的cpp產生的obj檔案都只有對應的符號而沒有函數體,如t1.obj、t2.obj就只有符號,當最後連結時IDE會把
a.obj的Foo()函數體連結進exe檔案中,並把t1.obj、t2.obj中的Foo符號轉換成對應在函數體exe檔案中的地址。
另外:當變數放在a.h中會變成全域變數的定義,如何讓它變為聲明呢?
例如: 我們在a.h中加入:class CFoo{};CFoo* obj;
這時按f7進行build時出現:
Linking...
t2.obj : error LNK2005: "class CFoo * obj" (?obj@@3PAVCFoo@@A) already defined in t1.obj
一個好辦法就是在a.cpp中定義此變數( CFoo* obj,然後拷貝此定義到a.h檔案中並在前面加上extern(extern CFoo* obj
如此就可通過了。當然extern也可以在任何調用此變數的位置之前聲明,不過強烈建議不要這麼作,因為到處作用extern,會
導致介面不統一。良好的習慣是介面一般就放到對應的標頭檔。
b. 將a.h中的定義修改成內部連結,即加上inline關鍵字,這時每個t1.obj和t2.obj都存放有一份Foo函數體,但它們不是外部
符號,所以不會被別的obj檔案引用到,故不存在衝突。(提示:把上述程式改來試試)
另外我作了個實驗來驗證”vc是把是否是外部符號的標誌記錄在obj檔案中的“(有點繞口)。可以看看,如下:
(1)檔案內容:
// 檔案main.cpp內容:
void main(){}
// 檔案t1.cpp內容:
#include "a.h"
void Test1(){ Foo(); }
// 檔案t2.cpp內容:
#include "a.h"
void Test2(){ Foo(); }
// 檔案a.h內容:
inline void Foo( ){ }
(2) 選t1.cpp按ctrl+f7單獨編譯,並把編譯後的t1.obj修改成t1.obj_inline
(3) 選t2.cpp按ctrl+f7單獨編譯,並把編譯後的t2.obj修改成t2.obj_inline
(4) 把除了t1.obj_inline及t2.obj_inline外的其它編譯產生的檔案刪除。
(5) 修改a.h內容為:void Foo( ){ },使之變為非內嵌函式作測試
(6) rebuild all所有檔案。這時提示:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (?Foo@@YAXXZ) already defined in t1.obj
Debug/cle.exe : fatal error LNK1169: one or more multiply defined symbols found
(7) 好,看看工程目錄下的debug目錄中會看到新產生的obj檔案。
下面我們來手工連結看看,
開啟菜單中的project - setting - Link,拷貝Project options下的所有內容,如下:
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib
odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept
把它修改成:
Link.exe kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib
uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept Debug/t1.obj Debug/t2.obj Debug/main.obj
pause
注意前面多了Link.exe,後面多了Debug/t1.obj Debug/t2.obj Debug/main.obj以及
最後一個pause批處理命令,然後把它另存到工程目錄(此目錄下會看到debug目錄)下起名為link.bat
運行它,就會看到:
t2.obj : error LNK2005: "void __cdecl Foo(void)" (?Foo@@YAXXZ) already defined i
n t1.obj
Debug/cle.exe : fatal error LNK1169: one or more multiply defined symbols found
很好,我們連結原來的obj檔案得到的效果跟在vc中用rebuild all出來的效果一樣。那麼現在如果
我們把備份出來的t1.obj_inline覆蓋t1.obj而t2.obj_inline覆蓋t2.obj再手動連結應該會是
不會出錯的,因為原t1.obj_inline及t2.obj_inline中存放的是內部連結符號。好運行Link.bat,果然
不出所料,連結成功了,看看debug目錄下多出了一個exe檔案。這就說明了內或外符號在obj有標誌標識!
(提示:上述為什麼不用vc的f7build連結呢,因為檔案時間改變了,build會重建新的obj,
所以我們用手動連結保證obj不變)[注bj資訊可用dumpbin.exe查看]
4.#include規則:
有很多人不知道#include 檔案該放在何處?
1). 增強組件自身的完整性:
為了保證組件完整,組件的cpp實現檔案(如test.cpp)中第一個#include的應當是它自身對應的標頭檔(如test.h)。
(除非你用先行編譯標頭檔, 先行編譯頭必須放在第一個)。這樣就保證了該組件標頭檔(test.h)所必須依賴的其它介面(如a.h等)要放到它對應的檔案頭中(test.h),而不是在cpp中(test.cpp)把所依賴的其它標頭檔(a.h等)移到其自身對應的標頭檔(test.h等)之前(因為這樣強迫其它包括此組件的標頭檔(test.h)的檔案(b.cpp)也必須再寫一遍include(即b.cpp若要#include "test.h"也必須#include
"a.h")”。另外我們一般會盡量減少檔案頭之間的依賴關係,看下面:
2). 減少組件之間的依賴性:
在1的基礎上盡量把#include到的檔案放在cpp中包括。
這就要求我們一般不要在標頭檔中直接引用其它變數的實現,而是把此引用搬到實現檔案中。
例如:
// 檔案foo.h:
class CFoo{
void Foo(){}
};
// 檔案test.h:
#include "foo.h"
class CTest{
CFoo* m_pFoo;
public:
CTest() : m_pFoo(NULL){}
void Test(){ if(m_pFoo){ m_pFoo->Foo();}}
...........
};
// 檔案test.cpp:
#include "test.h"
.....
如上檔案test.h中我們其實可以#include "foo.h"移到test.cpp檔案中。因為CFoo* m_pFoo我們只想在組件CTest中用到,
而將來想用到CTest組件而包括test.h的其它組件沒有必要見到foo.h介面,所以我們用前向聲明修改原檔案如下:
// 檔案foo.h:
class CFoo{
public:
void Foo(){}
};
// 檔案test.h:
class CFoo;
class CTest{
CFoo* m_pFoo;
public:
CTest();
void Test();
//........
};
// 檔案test.cpp:
#include "test.h" // 這裡第一個放該組件自身對應的介面標頭檔
#include "foo.h" // 該組件用到了foo.h
CTest::CTest() : m_pFoo(0){
m_pFoo = new CFoo;
}
void CTest::Test(){
if(m_pFoo){
m_pFoo->Foo();
}
}
//.....
// 再加上main.cpp來測試:
#include "test.h" // 這裡我們就不用見到#include "foo.h"了
CTest test;
void main(){
test.Test();
}
3). 雙重包含衛哨:
在檔案頭中包括其它標頭檔時(如:#include "xx.h")建議也加上包含衛哨:
// test.h檔案內容:
#ifndef __XX1_H_
#include "xx1.h"
#endif
#ifndef __XX2_H_
#include "xx2.h"
#endif
......
雖然我們已經在xx.h檔案中開頭已經加過,但是因為編譯器在開啟#include檔案也
是需要時間的,如果在外部加上包含衛哨,對於很大的工程可以節省更多的編譯時間。
轉載於:http://www.cnblogs.com/shiney/archive/2011/10/20/2219084.html