multiple definition of XXX first defined here 的錯誤

將程式分成includeall.h、functions.h、main.cpp、functions.cpp with DevCpp Compiler
但compiler時會出現以下錯誤訊息:
multiple definition of 'vNumCount'
first defined here
multiple definition of 'AllCount'
first defined here

以下為檔案內容:
//============================================================================/
/***includeall.h***/
#include <cstdlib>
#include <iostream>
#include <vector>

/***functions.h***/
#ifndef function S_H
#define function S_H
#include "includeall.h"
using namespace std;
vector<int> vNumCount;
int AllCount;
int showCount();
#endif

/***functions.cpp***/
#include "includeall.h"
#include "functions.h"
using namespace std;
int showCount()
{
    vNumCount.assign(10,42);//指派10個元素為42給vNumCount
    vector<int>::iterator Iter = vNumCount.begin();
    AllCount = 0;
    for(Iter = vNumCount.begin(); Iter != vNumCount.end(); ++Iter) {
     cout << *Iter << '\0';
     ++AllCount;
    }

    return 0;
}

/***main.cpp***/
#include "includeall.h"
#include "functions.h"
using namespace std;
int main(int argc, char *argv[])
{
    showCount();
    cout << AllCount;
    system("PAUSE");
    return EXIT_SUCCESS;

//============================================================================/

#ifndef/#define 這個組合像你在頭檔裡這樣子用叫做 header guard.
除了知道 header guard 的原理外, 你還必須知道一個叫 "compilation unit" 的概念.

一個 "compilation unit" 通常就是一個 .cpp 檔.
這個 .cpp 檔可能有數十個或數百個 #include, 每一個 #include 的程式碼裡又可以有許多個 #include.
這一個 .cpp 加上所有的, 直接及間接的 #include, 就是一個 "compilation unit".

一個很重要的概念是:
"每一個 compilation unit 是個獨立的個體, 所有的 #define 只在一個 compilation unit 裡有作用."

在編譯器建構你的專案的時候, main.cpp 跟 function s.cpp 是分開編譯的,
所以這裡就有兩個 compilation units.
每一個 compilation unit 會有一個同名的 object 檔, extension 通常是 .obj 或 .o.

你在 function s.h 裡定義了一個 vNumCount 物件. 因為 main.cpp 及 function s.cpp 都 #include "functions.h",
所以在 main.obj 及 function s.obj 都會有這個物件的定義.

個別編譯的時候當然不會有問題, 因為 main.cpp 及 function s.cpp 是互不相干的兩個個別的 compilation units.
main.cpp 裡的 #define 不會影響到編譯器在編譯 function s.cpp 時的行為, 反之亦然.

問題的發生是在 link 的時候. Linker 必須把所有的 object 檔整合成一個執行檔, 過程中的工作包括把函式呼叫與函式定義連接起來, 把物件的使用連結到它定義的位址, ...等.

當 Linker 整合 main.obj 及 function s.obj 的時候, 它發現到這兩個 object 檔都有 vNumCount 這個物件的定義. 捨棄其中一個, 或把它們結合成一個都不是 linker 的工作, 既然 linker 無法解決這個問題, 當然就把球丟回給你了.

解決方法:
  - 物件只在 .cpp 檔裡定義, 而且其他的 .cpp 檔不直接或間接 #include 有物件定義的 .cpp 檔.
  - 儘量避免使用全域物件.

以你的問題來說, 把
  vector<int> vNumCount;

從 function s.h 移到 function s.cpp 就可以輕易的解決這個 linker 的問題了.

即使物件在多個 .cpp 檔裡都有用到, 也只在其中一個 .cpp 檔裡定義. 在 .h 檔則加用 extern 關鍵字來『宣告』物件. 所有用到這個物件的 .cpp 檔都 #include 這個 .h 檔.

比方說: 如果 main.cpp 及 function s.cpp 都有用到 vNumCount 這個物件, 那:

〔functions.h〕
  ...
  extern vector<int> vNumCount;
  ...

〔functions.cpp〕
  #include "functions.h"
  vector<int> vNumCount;
  ...

〔main.cpp〕
  #include "functions.h"
  ...

【註】
上面的例子, vNumCount 是在 function s.cpp 裡定義的, 也可以把它移到 main.cpp 裡. 但只有一個『定義』.

在 function s.h 裡面的 extern vector<int> vNumCount 不是『定義』, 它是個『宣告』.
『宣告』沒有實體, 它告訴編譯器: 「有這麼一個東西, 但在其它地方, 留待 linker 來解決」.

但最好的方法還是儘量少用或不用全域物件.

 

arrow
arrow
    全站熱搜

    BB 發表在 痞客邦 留言(1) 人氣()