main already defined in *.obj 解決方法
VC6如果想在stdafx.h中定義全域性變數,由於該標頭檔案會被include多次,所以,經常會出現以下經典的錯誤:already defined in StdAfx.obj。
解決方法:把該變數的定義int g_flag放到stdafx.cpp中,然後在使用的地方extern一下。假如你在CAADlg.cpp中使用了該變數g_flag,那麼就在CAADlg.cpp的首部,建構函式的定義之外,新增上 extern int g_flag;
許多Visual C++的使用者都碰到過LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found這樣的連結錯誤,而且通常是在使用第三方庫時遇到的。對於這個問題,有的朋友可能不知其然,而有的朋友可能知其然卻不知其所以然,那麼本文就試圖為大家徹底解開關於它的種種疑惑。
大家都知道,從C/C++源程式到可執行檔案要經歷兩個階段:(1)編譯器將原始檔編譯成彙編程式碼,然後由彙編器(assembler)翻譯成機器指令(再加上其它相關資訊)後輸出到一個個目標檔案(object file,VC的編譯器編譯出的目標檔案預設的字尾名是.obj)中;(2)連結器(linker)將一個個的目標檔案(或許還會有若干程式庫)連結在一起生成一個完整的可執行檔案。
編譯器編譯原始檔時會把原始檔的全域性符號(global symbol)分成強(strong)和弱(weak)兩類傳給彙編器,而隨後彙編器則將強弱資訊編碼並儲存在目標檔案的符號表中。那麼何謂強弱呢?編譯器認為函式與初始化了的全域性變數都是強符號,而未初始化的全域性變數則成了弱符號。比如有這麼個原始檔:
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{
return 0;
}
其中main、buf是強符號,p是弱符號,而errorno則非強非弱,因為它只是個外部變數的使用宣告。
有了強弱符號的概念,連結器(Unix平臺)就會按如下規則(參考[1],p549~p550)處理與選擇被多次定義的全域性符號:
規則1: 不允許強符號被多次定義(即不同的目標檔案中不能有同名的強符號);
規則2: 如果一個符號在某個目標檔案中是強符號,在其它檔案中都是弱符號,那麼選擇強符號;
規則3: 如果一個符號在所有目標檔案中都是弱符號,那麼選擇其中任意一個;
雖然上述3條針對的是Unix平臺的連結器,但據作者試驗,至少VC6.0的linker也遵守這些規則。由此可知多個目標檔案不能重複定義同名的函式與初始化了的全域性變數,否則必然導致LNK2005和LNK1169兩種連結錯誤。可是,有的時候我們並沒有在自己的程式中發現這樣的重定義現象,卻也遇到了此種連結錯誤,這又是何解?嗯,問題稍微有點兒複雜,容我慢慢道來。
眾所周知,ANSI C/C++ 定義了相當多的標準函式,而它們又分佈在許多不同的目標檔案中,如果直接以目標檔案的形式提供給程式設計師使用的話,就需要他們確切地知道哪個函式存在於哪個目標檔案中,並且在連結時顯式地指定目標檔名才能成功地生成可執行檔案,顯然這是一個巨大的負擔。所以C語言提供了一種將多個目標檔案打包成一個檔案的機制,這就是靜態程式庫(static library)。開發者在連結時只需指定程式庫的檔名,連結器就會自動到程式庫中尋找那些應用程式確實用到的目標模組,並把(且只把)它們從庫中拷貝出來參與構建可執行檔案。幾乎所有的C/C++開發系統都會把標準函式打包成標準庫提供給開發者使用(有不這麼做的嗎?)。
程式庫為開發者帶來了方便,但同時也是某些混亂的根源。我們來看看連結器(Unix平臺)是如何解析(resolve)對程式庫的引用的(參考[1],p556)。
在符號解析(symbol resolution)階段,連結器按照所有目標檔案和庫檔案出現在命令列中的順序從左至右依次掃描它們,在此期間它要維護若干個集合:(1)集合E是將被合併到一起組成可執行檔案的所有目標檔案集合;(2)集合D是所有之前已被加入E的目標檔案定義的符號集合;(3)集合U是未解析符號(unresolved symbols,即那些被E中目標檔案引用過但在D中還不存在的符號)的集合。一開始,E、D、U都是空的。
(1): 對命令列中的每一個輸入檔案f,連結器確定它是目標檔案還是庫檔案,如果它是目標檔案,就把f加入到E,並把f中未解析的符號和已定義的符號分別加入到U、D集合中,然後處理下一個輸入檔案。
(2): 如果f是一個庫檔案,連結器會嘗試把U中的所有未解析符號與f中各目標模組定義的符號進行匹配。如果某個目標模組m定義了一個U中的未解析符號,那麼就把m加入到E中,並把m中未解析的符號和已定義的符號分別加入到U、D集合中。不斷地對f中的所有目標模組重複這個過程直至到達一個不動點(fixed point),此時U和D不再變化。而那些未加入到E中的f裡的目標模組就被簡單地丟棄,連結器繼續處理下一輸入檔案。
(3): 當掃描完所有輸入檔案時如果U非空或者有同名的符號被多次加入D,連結器報告錯誤資訊並退出。否則,它把E中的所有目標檔案合併在一起生成可執行檔案。
上述規則針對的是Unix平臺連結器,而VC(至少VC6.0)linker則有相當的不同: 它首先依次處理命令列中出現的所有目標檔案,然後依照順序不停地掃描所有的庫檔案,直至U為空或者某遍(從頭到尾依次把所有的庫檔案掃描完稱為一遍)掃描過程中U、D無任何變化時結束掃描,此刻再根據U是否為空以及是否有同名符號重複加入D來決定是出錯退出還是生成可執行檔案。很明顯Unix連結器對輸入檔案在命令列中出現的順序十分敏感,而VC的演算法則可最大限度地減少檔案順序對連結的影響。作者不清楚Unix下新的開發工具是否已經改進了相應的做法,歡迎有實踐經驗的朋友補充這方面的資訊(補充於2005年10月10日: 經試驗,使用gcc 3.2.3的MinGW 3.1.0的連結器表現與參考[1]描述的一致)。
VC帶的編譯器是cl.exe,它有這麼幾個與標準程式庫有關的選項: /ML、/MLd、/MT、/MTd、/MD、/MDd。這些選項告訴編譯器應用程式想使用什麼版本的C標準程式庫。/ML(預設選項)對應單執行緒靜態版的標準程式庫(libc.lib);/MT對應多執行緒靜態版標準庫(libcmt.lib),此時編譯器會自動定義_MT巨集;/MD對應多執行緒DLL版(匯入庫msvcrt.lib,DLL是msvcrt.dll),編譯器自動定義_MT和_DLL兩個巨集。後面加d的選項都會讓編譯器自動多定義一個_DEBUG巨集,表示要使用對應標準庫的除錯版,因此/MLd對應除錯版單執行緒靜態標準庫(libcd.lib),/MTd對應除錯版多執行緒靜態標準庫(libcmtd.lib),/MDd對應除錯版多執行緒DLL標準庫(匯入庫msvcrtd.lib,DLL是msvcrtd.dll)。雖然我們的確在編譯時明白無誤地告訴了編譯器應用程式希望使用什麼版本的標準庫,可是當編譯器幹完了活,輪到連結器開工時它又如何得知一個個目標檔案到底在思念誰?為了傳遞相思,我們的編譯器就幹了點祕密的勾當。在cl編譯出的目標檔案中會有一個專門的區域(關心這個區域到底在檔案中什麼地方的朋友可以參考COFF和PE檔案格式)存放一些指導連結器如何工作的資訊,其中有一項就叫預設庫(default library),它指定了若干個庫檔名,當連結器掃描該目標檔案時將按照它們在目標模組中出現的順序處理這些庫名: 如果該庫在當前輸入檔案列表中還不存在,那麼便把它加入到輸入檔案列表末尾,否則略過。說到這裡,我們先來做個小實驗。寫個頂頂簡單的程式,然後儲存為main.c :
/* main.c */
int main() { return 0; }
用下面這個命令編譯main.c(什麼?你從不用命令列來編譯程式?這個......) :
cl /c main.c
/c是告訴cl只編譯原始檔,不用連結。因為/ML是預設選項,所以上述命令也相當於: cl /c /ML main.c 。如果沒什麼問題的話(要出了問題才是活見鬼!當然除非你的環境變數沒有設定好,這時你應該去VC的bin目錄下找到vcvars32.bat檔案然後執行它。),當前目錄下會出現一個main.obj檔案,這就是我們可愛的目標檔案。隨便用一個文字編輯器開啟它(是的,文字編輯器,大膽地去做別害怕),搜尋"defaultlib"字串,通常你就會看到這樣的東西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈,沒錯,這就
是儲存在目標檔案中的預設庫資訊。我們的目標檔案顯然指定了兩個預設庫,一個是單執行緒靜態版標準庫libc.lib(這與/ML選項相符);一個是oldnames.lib(它是為了相容微軟以前的C/C++開發系統,基本不用了,為了簡化討論可以忽略它)。另外,如果在源程式中用了
/* xxxx代表實際的庫檔名 */
#pragma comment(lib,"xxxx")
編譯指示命令(compiler directive)指定要連結的庫,那麼這個資訊也會被儲存到目標檔案的預設庫資訊項中,且位於預設標準庫之前。如果有多個這樣的命令,那麼對應庫名在目標檔案中出現的順序與它們在源程式中出現的順序完全一致(且都在預設標準庫之前)。
VC的連結器是link.exe,因為main.obj儲存了預設庫資訊,所以可以用
link main.obj libc.lib
或者
link main.obj
來生成可執行檔案main.exe,這兩個命令是等價的。但是如果你用
link main.obj libcd.lib
的話,連結器會給出一個警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library",因為你顯式指定的標準庫版本與目標檔案的預設值不一致。通常來說,應該保證連結器合併的所有目標檔案指定的預設標準庫版本一致,否則編譯器一定會給出上面的警告,而LNK2005和LNK1169連結錯誤則有時會出現有時不會。那麼這個有時到底是什麼時候?呵呵,彆著急,下面的一切正是為喜歡追根究底的你準備的。
建一個原始檔,就叫mylib.c,內容如下:
/* mylib.c */
#include <stdio.h>
void foo(void)
{
printf("%s","I am from mylib!\n");
}
用
cl /c /MLd mylib.c
命令編譯,注意/MLd選項是指定libcd.lib為預設標準庫。lib.exe是VC自帶的用於將目標檔案打包成程式庫的命令,所以我們可以用
lib /OUT:my.lib mylib.obj
將mylib.obj打包成庫,輸出的庫檔名是my.lib。接下來把main.c改成:
/* main.c */
void foo(void);
int main()
{
foo();
return 0;
}
用
cl /c main.c
編譯,然後用
link main.obj my.lib
進行連結。這個命令能夠成功地生成main.exe而不會產生LNK2005和LNK1169連結錯誤,你僅僅是得到了一條警告資訊:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我們根據前文所述的掃描規則來分析一下連結器此時做了些啥(加一個/VERBOSE選項就可以看到詳盡的連結過程,但要注意,幾乎所有的C編譯器都會在符號前加一個下劃線後再輸出,所以在目標檔案和連結輸出資訊中看到的符號名都比在源程式中見到的多出一個'_',此點不可不察。)。
一開始E、U、D都是空集。連結器首先掃描main.obj,把它的預設標準庫libc.lib加入到輸入檔案列表末尾,它自己加入E集合,同時未解析的foo加入U,main加入D。接著掃描my.lib,因為這是個庫,所以會拿當前U中的所有符號(當然現在就一個foo)與my.lib中的所有目標模組(當然也只有一個mylib.obj)依次匹配,看是否有模組定義了U中的符號。結果mylib.obj確實定義了foo,於是它加入到E,foo從U轉移到D,未解析的printf加入到U,指定的預設標準庫libcd.lib也加到輸入檔案列表末尾(在libc.lib之後)。不斷地在my.lib庫的各模組上進行迭代以匹配U中的符號,直到U、D都不再變化。很明顯,現在就已經到達了這麼一個不動點,所以接著掃描下一個輸入檔案,就是libc.lib。連結器發現libc.lib裡的printf.obj裡定義有printf,於是printf從U移到D,printf.obj加入到E,它定義的所有符號加入到D,它裡頭的未解析符號加入到U。如果連結時沒有指定/ENTRY(程式入口點選項),那麼連結器預設的入口點就是函式mainCRTStartup(GUI程式的預設入口點則是WinMainCRTStartup),它在crt0.obj中被定義,所以crt0.obj及它直接或間接引用的模組(比如malloc.obj、free.obj等)都被加入到E中,這些目標模組指定的預設庫(只crt0init.obj指定了kernel32.lib)加到輸入檔案列表末尾,同時更新U和D。不斷匹配libc.lib中各模組直至到達不動點,然後處理libcd.lib,但是它裡面的所有目標模組都沒有定義U中的任何一個符號,所以連結器略過它進入到最後一個輸入檔案kernel32.lib。事實上,U中已有和將要加入的未解析符號都可以在其中找到定義,那麼當處理完kernel32.lib時,U必然為空,於是連結器合併E中的所有模組生成可執行檔案。
上文描述了雖然各目標模組指定了不同版本的預設標準庫但仍然連結成功的例子,接下來你將目睹因為這種不嚴謹而導致的悲慘失敗。
修改mylib.c成這個樣子:
#include <crtdbg.h>
void foo(void)
{
// just a test , don't care memory leak
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );
}
其中_malloc_dbg不是ANSI C的標準庫函式,它是VC標準庫提供的malloc的除錯版,與相關函式配套能幫助開發者抓各種記憶體錯誤。使用它一定要定義_DEBUG巨集,否則前處理器會把它自動轉為malloc。繼續用
cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj
編譯打包。當再次用
link main.obj my.lib
進行連結時,我們看到了什麼?天哪,一堆的LNK2005加上個貴為"fatal error"的LNK1169墊底,當然還少不了那個LNK4098。連結器是不是瘋了?不,你冤枉可憐的連結器了,我拍胸脯保證它可是一直在盡心盡責地照章辦事。
一開始E、U、D為空,連結器掃描main.obj,把libc.lib加到輸入檔案列表末尾,把main.obj加進E,把foo加進U,把main加進D。接著掃描my.lib,於是mylib.obj加入E,libcd.lib加到輸入檔案列表末尾,foo從U轉移到D,_malloc_dbg加進U。然後掃描libc.lib,這時會發現libc.lib裡任何一個目標模組都沒有定義_malloc_dbg(它只在除錯版的標準庫中存在),所以不會有任何一個模組因為_malloc_dbg而加入E。但因為libc.lib中的crt0.obj定義了預設入口點函式mainCRTStartup,所以crt0.obj及它直接或間接引用的模組(比如malloc.obj、free.obj等)都被加入到E中,這些目標模組指定的預設庫(只crt0init.obj指定了kernel32.lib)加到輸入檔案列表末尾,同時更新U和D。不斷匹配libc.lib中各模組直至到達不動點後再處理libcd.lib,發現dbgheap.obj定義了_malloc_dbg,於是dbgheap.obj加入到E,它的未解析符號加入U,它定義的所有其它符號加入D,這時災難便來了。之前malloc等符號已經在D中(隨著libc.lib裡的malloc.obj加入E而加入的),而dbgheap.obj及因它而引入的其它模組又定義了包括malloc在內的許多同名符號,導致了重定義衝突。所以連結器在處理完所有輸入檔案(是的,即使中途有重定義衝突它也會處理所有的檔案以便生成一個完整的衝突列表)後只好報告: 這活兒沒法兒幹。
現在我們該知道,連結器完全沒有責任,責任在我們自己的身上。是我們粗心地把預設標準庫版本不一致的目標檔案(main.obj)與程式庫(my.lib)連結起來,引發了大災難。解決辦法很簡單,要麼用/MLd選項來重編譯main.c;要麼用/ML選項重編譯mylib.c;再或者乾脆在連結時用/NODEFAULTLIB:XXX選項忽略預設庫XXX,但這種方法非常不保險(想想為什麼?),所以不推薦。
在上述例子中,我們擁有庫my.lib的原始碼(mylib.c),所以可以用不同的選項重新編譯這些原始碼並再次打包。可如果使用的是第三方的庫,它並沒有提供原始碼,那麼我們就只有改變自己程式的編譯選項來適應這些庫了。但是如何知道庫中目標模組指定的預設庫呢?其實VC提供的一個小工具便可以完成任務,這就是dumpbin.exe。執行下面這個命令
dumpbin /DIRECTIVES my.lib
然後在輸出中找那些"Linker Directives"引導的資訊,你一定會發現每一處這樣的資訊都會包含若干個類似"-defaultlib:XXXX"這樣的字串,其中XXXX便代表目標模組指定的預設庫名(注意,如果在編譯時指定了/Zl選項,那麼目標模組中將不會有defaultlib資訊)。
知道了第三方庫指定的預設標準庫,再用合適的選項編譯我們的應用程式,就可以避免LNK2005和LNK1169連結錯誤。喜歡IDE的朋友,你一樣可以到 "Project屬性" -> "C/C++" -> "程式碼生成(code generation)" -> "執行時庫(run-time library)" 項下設定應用程式的預設標準庫版本,這與命令列選項的效果是一樣的。
參考資料:
[1] 《Computer Systems: A Programmer's Perspective》
著: Randal E. Bryant, David R. O'Hallaron
電子工業出版社,2004
.net中的編譯問題,出現諸如:
(MSVCR80D.dll) : error LNK2005: __CrtDbgReport already defined in libcmtd.lib(dbgrpt.obj)
msvcrtd.lib(MSVCR80D.dll) : error LNK2005: _memmove already defined in libcmtd.lib(memmove.obj)
的解決辦法:
程式設計中經常能遇到LNK2005錯誤——重複定義錯誤,其實LNK2005錯誤並不是一個很難解決的錯誤。弄清楚它形成的原因,就可以輕鬆解決它了。
造成LNK2005錯誤主要有以下幾種情況:
1.重複定義全域性變數。可能存在兩種情況:
A、對於一些初學程式設計的程式設計師,有時候會以為需要使用全域性變數的地方就可以使用定義申明一下。其實這是錯誤的,全域性變數是針對整個工程的。正確的應該是在一個CPP檔案中定義如下:int g_Test;那麼在使用的CPP檔案中就應該使用:extern int g_Test即可,如果還是使用int g_Test,那麼就會產生LNK2005錯誤,一般錯誤錯誤資訊類似:AAA.obj error LNK2005 int book c?[email protected]@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等其他庫,逐一嘗試。
關於編譯器的相關處理過程,參考:
這就是我所遇到過的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)" ([email protected]@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" ([email protected]@[email protected]@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)" ([email protected]@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)" ([email protected]@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檔案也
是需要時間的,如果在外部加上包含衛哨,對於很大的工程可以節省更多的編譯時間。
相關推薦
main already defined in *.obj 解決方法
VC6如果想在stdafx.h中定義全域性變數,由於該標頭檔案會被include多次,所以,經常會出現以下經典的錯誤:already defined in StdAfx.obj。 解決方法:把該變數的定義int g_flag放到stdafx.cpp中,然後在使用的地方ext
c++ 變數不要定義在.h 標頭檔案當中(main already defined in *.obj 解決方法)
原文地址:http://blog.csdn.NET/tianwailaibin/article/details/6239380 原部落格地址:http://blog.csdn.net/u010536615/article/details/50250747 標頭檔案中定義變數
VC6 出現“already defined in *.obj”錯誤的解決方法
通常在VC6 工程中某個標頭檔案中定義了全域性變數,而這個全域性變數要在很多個.cpp檔案中使用時,就會出現“……already defined in *.obj”的錯誤提示,解決方法是: 把錯誤提示中提到了全域性變數(或全域性函式)的定義放到一個.cpp檔案中。在其他.cpp檔案中使用時,宣告extern
already defined in *.obj“符號已定義”問題原理及解決方案
VC6如果想在stdafx.h中定義全域性變數,由於該標頭檔案會被include多次,所以,經常會出現以下經典的錯誤:already defined in StdAfx.obj。 解決方法:把該變數的定義int g_flag放到stdafx.cpp中,然後在使用的地方e
libcmtd.lib(crt0dat.obj) : error LNK2005: _exit already defined in msvcrtd.lib(MSVCRTD.dll) 編譯錯誤解決方法
[問題描述] 今天在VC6下編譯G279軟體時,已編譯通過,連結時報一下錯誤: Linking...libcmtd.lib(crt0dat.obj) : error LNK2005: _exit already defined in msvcrtd.lib(MSVCRTD.d
開啟redis-server提示 # Creating Server TCP listening socket *:6379: bind: Address already in use--解決方法
tools spec clipboard word ron replace use name pan 在bin目錄中開啟Redis服務器,完整提示如下: [java] view plain copy 3496:C 25 Apr 00:56:48.717
Error LNK2005:exist already defined in msvcrt.lib(MSVCR100.dll)_LIBCMTD.lib 解決方案
Project -- Setting -- C/C++ -- C++ Language Enable Run-Time Type Information(RTTI) 此項必須打勾! 但現在Debug版編譯ok,Release版編譯報錯: Linking... Crea
CCritSec::CCritSec(void) already defined in 問題的解決
各位大蝦,本人在編譯DirectShow工程時,Release版是好的,但在編譯Debug版本時出問題,請教怎麼樣解決? strmbasd.lib(wxutil.obj) : error LNK2005: "public: __thiscall CCritSec::CCri
bind:address already in use解決方法
每次修改了原始碼並再次編譯執行時,常遇到下面的地使用錯誤: Cann’t bind server socket ! : Address already in use 雖然用Ctrl+C強制結束了程序,但錯誤依然存在,用netstat -an |grep 5120和ps aux |grep 5120都還能看到
Python3 Address already in use 解決方法
1、檢視使用埠號netstat -ntlp 2、根據埠號找到pid 3、殺死程式 kill -9 pid 4、重新啟動程式 簡單粗暴 我使用python3時編寫Socket,linux系統下使用ctrl+z結束程式後,重啟程式繼續報錯,Address already
error LNK2005: __cinit already defined in libcmt.lib(crt0dat.obj)
用靜態庫去編譯,若出現下面錯誤:LIBC.lib(crt0dat.obj) : error LNK2005: __cinit already defined in libcmt.lib(crt0dat.obj)LIBC.lib(crt0dat.obj) : error LNK
SVN Attempted to lock an already-locked dir異常解決方法
令行 iss 客戶 技術 emp sso client 刪除 works Attempted to lock an already-locked dir異常解決方法 eclipse或myeclipse用svn提交的時候報錯: Attempted to lock an
require(): open_basedir restriction in effect. 解決方法
pla com user led root 提示 重新啟動 bubuko fatal 在linux服務器部署thinkphp5的時候PHP報了這個錯誤, 如下: Warning: require(): open_basedir restriction in effec
python中使用pip安裝報錯:Fatal error in launcher... 解決方法
python安裝了2和3版本在 cmd 中用pip報的錯誤為:Fatal error in launcher:Unable to create process using 這是因為你安裝了python3和python2在你的windows下,並且在環境變數中分別配置了pip,可是並沒有區分pip3還是p
Maven:Failed to create a Maven project ‘…pom.xml’ already exists in VFS 解決
轉自:https://blog.csdn.net/chenyufeng1991/article/details/73724686 有時候我們在建立Maven專案的時候會出現上述的問題,導致Maven專案建立失敗,報錯的提示如下: &nbs
終端出現 You have new mail in ... 的解決方法
1.檢視待發送的email 使用mail檢視,可以看到有email列表及儲存電郵的路徑。 Mail version 8.1 6/6/93. Type ? for help. "/var/mail/xjzhang": 39573 messages 39573 new >N 1 [em
error LNK2005:XXXX already defined in XXX.lib
設定工程時,出現類似如下錯誤: msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<ch
Error LINK2005: already defined in libcmt.lib
這個錯誤是微軟設計錯誤,因此如果遇到這個錯誤,我們只能躲過這個連結錯誤。具體的辦法就是將那個提示出錯的庫放到另外一個庫的前面。另外選擇不同的C函 數庫,可能會引起這個錯誤。MS, C有兩種C函式庫,一種是普通的函式庫:LIBC.LIB,不支援多執行緒。另外一種是支援多執行緒
debian9.6開機提示: Driver 'pcspkr' is already registered, aborting,解決方法
debian9.6開機之後有這個提示, Debian: Driver 'pcspkr' is already registered, aborting 解決方法: 開機進入, ## ls dkms.conf qemu-blacklist.conf ## sudo cp q
Error creating bean with name 'sqlSessionFactory' defined in file解決
十月 28, 2018 10:24:16 上午 org.apache.catalina.core.StandardContext listenerStart 嚴重: Exception sending context initialized event to listener