徹底理解C++在.h頭文件中定義變量導致 multiple definition

說明:出現這個錯誤,請你先檢查重複定義的變量是否是定義在了.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加入到工程中一起鏈接.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章