一直对编译这个事情不是太明白,想好好学习一下,今天自己写东西试验,遇到了这么个问题,想请教一下,先贴代码
// test.h
#ifndef JUST_FOR_TEST_H_
#define JUST_FOR_TEST_H_
struct Test {
static int i;
void t();
};
int Test::i = 0;
#endif
// test.cpp
#include "test.h"
void Test::t() { }
// main.cpp
#include "test.h"
int main() {
Test t;
t.t();
}
本来觉得这个是很简单的,但是链接的时候报了错,提示是
ld: 1 duplicate symbol for architecture x86_64
将test.h中静态成员的定义放到cpp文件中就好了,但是我很不理解,不是使用了头文件保护吗?这样不是已经确保了一个头文件在一个工程中只会被包含一次吗?是我哪里的理解出了偏差了吗?希望各位高手可以指点一下,本人菜鸡,不是学计算机出身的,所以问的问题可能很傻,如果可以的话希望可以讲的详细一点,如果可以介绍两本学习的书就更好了,先谢过了!
短答案:
headguard只能防止頭檔被二次包含。你編譯時的問題是,i在不同的編譯單元中被定義。
多數ide編譯時會將所有的原始檔分別編譯成目標文件,最後由連接器將目標文件連接起來。目標檔案之間的連接關係是由對應來源檔案中的include來推斷的。頭檔不參與編譯。此處IDE產生的編譯腳本差不多是
clang++ main.cpp test.cpp
。當然除此之外還有很多工程設定裡的編譯選項。你的例子中,編譯器會編譯test.cpp和main.cpp,產生目標檔。然後連接器會將這兩個目標檔案連接起來。由於靜態成員變數i定義在頭檔中,並且兩個原始檔都包含了這個頭檔。結果就是i在兩個編譯單元中分別被定義了一次。最後連接時就會出現連線錯誤。
長答:
你遇到的連結錯誤其實是因為違反ODR引起的。
ODR約定:
但除此之外還有很多的特例。這裡就不列舉了(主要因為是我列不全…)。translation unit是編譯單元,總共有幾個編譯單元由編譯指令決定。編譯單元的編譯結果稱為目標檔。 definition是定義,在這個例子中是靜態成員變數
編譯指令:int Test::i = 0;
的定義,根據ODR,這個定義在整個程式中只能出現一次。i
program是程序,程式由眾多的目標檔案連結而成。
此次編譯一共有兩個編譯單元main.cpp和test.cpp。他們會被分別編譯成目標檔。clang++ main.cpp test.cpp -o main
是預編譯宏,他會引入對應檔的內容,並且所有的預編譯指令都會在原始碼被編譯前執行。也就是說編譯單元"test.cpp"的內容(或說clang編譯器收到的程式碼)為:
編譯單元"main.cpp"的內容為:#include
違反ODR就是在這個連結時發生的。
如果
int Test::i = 0;
的定義在test.cpp。那麼在main.cpp中就不會出現這個定義,也就沒有違反ODR。