【C++】變數定義在.h標頭檔案導致 multiple definition of 的解決方法和根本原因
說明:出現這個錯誤,請你先檢查重複定義的變數是否是定義在了.h標頭檔案中,如果是,請您耐心的看完這篇文章,他會告訴你錯誤的根本原因。
如果你很著急,不想弄清楚原因,請直接按下面的方法更改:
假設重複定義的變數是int a,且你定義在了b.h,想作為全域性變數使用,那麼:
1.刪除b.h中的int a
2.在b.cpp中加入a的定義int a;
3.在b.h中加入 extern int a;
4.在要使用a的cpp檔案中加入#include "b.h"
1.引言
學習計算機那麼久了,也程式設計這麼久了,但是一直都沒有徹底弄清楚標頭檔案互相包含時,為什麼有時候會出錯,出現重複定義,有時候確又能夠正常編譯連結,今天仔細研究了一下這個問題,將結論記錄下來。
2.編譯
編譯,大家都知道,最後就會產生一個.o檔案,我以前估計就理解到這個水平。於是我常常想不通一個問題,比如MFC的一個大工程,裡面有a.cpp,a.h,b.cpp,b.h,那他們是合在一起編譯嗎?那互相包含會不會出錯?
結論:編譯是針對一個檔案來說的,比如有a.cpp,a.h,b.cpp,b.h,他們之間的編譯是沒有關係的,a.cpp和a.h會產生一個a.o,他的編譯和b完全沒有關係,同樣b.cpp和b.h產生的b.o和a也沒有關係(除了include包含的情況)。
3.連結
連結就很有可能是很多.o檔案一起連結,組成一個可執行檔案,例如g++ a.cpp b.cpp -o c,這樣就是先單獨編譯了a.cpp和b.cpp並將產生的a.o,b.o連結為c
4.標頭檔案包含的問題
我們都知道,ifndef是為了防止標頭檔案重複包含,比如
a.h:
#include "b.h"
#include "c.h"
b.h:
#include "c.h"
c.h:
內容....
這樣的話,我們編譯a.cpp肯定會出現問題,因為a.h裡面包含了2次c.h,而c.h裡面沒有加入#ifndef之類的語句
所以我們需要將c.h改為下面的形式:
c.h
#ifndef _C_HEADER
#define _C_HEADER
.....內容
#endif
這樣的話,編譯a.cpp就沒錯了,但是我們要注意,#ifndef只是針對某一個檔案,而不是一個工程,比如你在a.h裡面包含了c.h,在b.h裡面也包含了c.h,這樣不管你的c.h有沒有加入#ifndef,你的c.h檔案在a.h和b.h當中都會展開,而不是說a和b因為是一個工程,所以只展開一次。
5.extern的用法
首先,標頭檔案.h中不適宜定義變數,我們都知道定義全域性變數的常規使用方法是在.cpp檔案中定義變數,在.h檔案中用extern申明,這是為什麼呢?看下面的例子:
比如在MFC的一個大工程中,我想定義一個全域性變數int a,因為所有的檔案都包含stdafx.h,我們考慮寫在裡面
第一種寫法:
stdafx.h:
int a
........
a.cpp
#include "stdafx.h"
extern int a;
a = 100;
......
b.cpp
#include "stdafx.h"
extern int a;
a = 200;
.......
接下來,
編譯,沒錯耶!!!好開心
連結,怎麼出錯了?!!!重複定義a?好難過....
我就是這麼難過....終於弄除了原因:
原因:在編譯的時候,由於是一個一個檔案為單位,所以a.cpp a.h被單獨編譯產生a.o,這樣在編譯的時候,由於他包含了stdafx.h,所以在a.o中,已經有int a的定義了
而b.cpp b.h也是被單獨編譯的,產生b.o裡面也有一個int a的定義,所以出現了重複定義的錯誤。
正確的寫法
stdafx.cpp
int a;
stdafx.h
extern int a;
a.cpp
#include"stdafx.h"
a=100;
......
b.cpp
#include"stdafx.h"
......
a= 200;
這樣會發現編譯連結都正確,這是為什麼呢?
因為在編譯的時候,由於mfc會自己預設編譯stdafx.h和stdafx.cpp產生stdafx.o檔案,然後我們的a.cpp編譯的時候包括了stdafx.h,但是stdafx.h裡面只有extern a,這就說明,我們在下面使用到的a都是在其他檔案中定義的,同樣的b.cpp編譯時,也不會定義a,所以編譯出來的a.o和b.o都不包含a的定義。最後連結,連結的時候,由於這是一個工程,所以mfc會把stdafx.o,a.o,b.o連結在一起,這樣我們的a就定義了一次,在stdafx.o中。
看到這裡,你可能會覺得奇怪,我都不知道我按下vc6.0的編譯按鈕,是編譯了那麼多檔案,按下連結按鈕,又是把所有檔案連結到一起了,這些可能需要進一步瞭解makefile之類的東西。
6.結論
1.編譯是針對一個一個檔案來說的,而連結則是針對一個工程所有的.o檔案而言的。
2.#ifndef只是對防止一個檔案的重複編譯有效
3.全域性變數最好在.cpp檔案中定義,在.h檔案中加上extern申明,因為在.h檔案中定義,容易在連結時造成變數重定義。
4.注意mfc中會自動編譯stdafx,並將stdafx.o加入到工程中一起連結.