今天下午遇到一個頭文件相互包含而導致的編譯問題,花了我不少時間去調試沒找到問題,最後晚上跟師兄討論不少時間,突然有所頓悟!
我把問題脫離於項目簡單描述一下:我寫了一個函數 bool func(ClassA* CA)
需要加到項目中,我就把這個函數的聲明放到 head1.h
中,函數參數類型 ClassA 定義在另一個頭文件 head2.h
中,因此我需要在 head1.h
中包含 head2.h
;而 head2.h
中之前又包含了 head1.h
,這樣就構成了一種頭文件相互包含的場景。再加上一些其它的聲明與定義,就構成了這樣的一個文件結構:
#ifndef __HEAD_1_H__ #define __HEAD_1_H__ #include "head2.h" #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h #endif
#ifndef __HEAD_2_H__ #define __HEAD_2_H__ #include "head1.h" class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; #endif
那麼,現在另有兩個源文件
#include "head1.h" //... some source code
#include "head2.h" //... some source code
整個項目會分別編譯這兩個源文件,編譯完之後會報錯,大致意思是 ClassA 和 VAR_MACRO 沒有定義,那麼問題就比較奇怪了,每個頭文件都分別引用了另一個頭文件,為什麼會出現未定義呢?
我們都知道 C/C++ 中頭文件開始習慣使用 #ifndef ... #define ... #endif
這樣的一組預處理標識符來防止重復包含,例如上面的問題中我也使用了,如果不使用的話,兩個頭文件相互包含,就出現遞歸包含。這個很好理解就不多敘。
回到問題本身,我在微博上貼出了這個問題,有人說在 head1.h
的函數前加上 ClassA
A;
的前置聲明,至於為什麼要加,估計很多人都沒理解...
對於 source1.cpp
,它包含了頭文件 head1.h
,那麼在編譯之前,在 source1.cpp
中展開 head1.h
,而 head1.h
又包含了 head2.h
,
那麼也展開它,這時 source1.cpp
就變成類似下面這樣:
class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h //... source1.cpp source code
看到沒,這地方 func 函數之前有 ClassA 類型的定義,根本沒必要像有些人說的那樣加上 ClassA CA;
這樣的前置聲明。
我們再展開 source2.cpp
看看:
#define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h class ClassA{ int mVar; void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions }; //... source2.cpp source code
這時問題就很清楚了,func 函數聲明之前並沒有發現 ClassA 類型定義,該定義在函數聲明的後面,這時候如果能在head1.h
的函數聲明之前加上 ClassA
CA;
的前置聲明,就不會在編譯的時候報找不到 ClassA 的定義的錯誤了。
再回到 source1.cpp
展開的源碼看看,是不是一下子明白了為什麼報找不到 VAR_MACRO 的定義的錯誤了?修改方法也簡單,把宏定義拉到 #include
"head2.h"
語句之前就 OK 了。
現在回頭想想這個問題,其實是個非常簡單的頭文件包含的問題,如果了解一些編譯器的預編譯過程,錯誤原理也很簡單。但為什麼我卡在這個問題很長時間,原因有以下幾點:
總的來說,遇到問題不要慌,保持大腦清醒,把加一行或減一行代碼期望就能碰運氣編譯通過的時間拿來分析問題更有效,解決問題之後一定確定自己是否知其然亦知其所以然!
Google找了一圈,沒找到有價值的資料....