最近在看一個PHP的擴展源碼,編譯的時候的遇到一個問題:
ld: 1 duplicate symbol for architecture x86_64
仔細看了一下源碼,發現在頭文件中 出現了全局變量的定義
ZEND_DECLARE_MODULE_GLOBALS(xx)
簡單開來,可以這麼理解
// t1.h #ifndef T1_H #define T1_H int a = 0; #endif //------------------ //t1.c #include "t1.h" #include "t2.h" int main(){ return 0; } //----------------- //t2.h #include "t1.h" //empty //---------------- //t2.c #include "t2.h" //empty //-------
那麼第一個問題,#ifndef 的這個宏 是否應該防止了 多重定義?
答案:是。但是是在單個編譯單元中(wiki translation unit)。
我們知道,一個完整的編譯的過程是經過
one.c --> PREPROCESSOR -> tmp.c(temporary) -> COMPILER -> one.obj -> LINKER -> one.exe
這三個過程的,而在預編譯階段,便會把include的文件展開,我們使用cc -E 命令來查看t1.c的預編譯的結果:
➜ t cc -E t1.c # 1 "t1.c" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 321 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "t1.c" 2 # 1 "./t1.h" 1 int a = 0; # 3 "t1.c" 2 # 1 "./t2.h" 1 # 4 "t1.c" 2 int main(void){ return 0; }
看到編譯器把 t1.h 做了展開,我們看到了 a的定義。
而在t2.c 的預編譯結果裡,我們同樣看到了a的展開定義:
➜ t cc -E t2.c # 1 "t2.c" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 321 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "t2.c" 2 # 1 "./t2.h" 1 # 1 "./t1.h" 1 int a = 0; # 2 "./t2.h" 2 # 2 "t2.c" 2
那麼,為神馬 #ifdef ( include guards )沒有起到 防止重定義的作用呢?
原因在於 include guards 只在同一個編譯單元(一個c文件和include的文件的編譯過程)內起作用,兩個編譯單元是編譯過程是分開的,所以無法察覺到另外一個裡面的#ifdefine內容
t1.c -> t1.s -> t2.o \ *-> - t.otu / t2.c -> t2.s -> t2.o
所以,在頭文件中是不應該define 變量,只應該declare。
那麼如果我偏要呢?
如果是函數,有人給出這麼個辦法,添加inline或者static 關鍵字。或者有人直接這麼搞:
#ifdef DEFINE_GLOBALS #define EXTERN #else #define EXTERN extern #endif EXTERN int global1; EXTERN int global2;
那麼,回到最初的問題,頭文件中確實是不可以定義變量的,而且我也沒有看到類似特殊的宏定義處理辦法,那麼這個問題他是如何處理的呢?難道在編譯階段處理?
且等下節。。。