C程序編譯時常出現類似xxx redefinition錯誤,除了模塊間的命名沖突(命名污染及static),問題多數與頭文件管理有關。大型C工程的頭文件管理很麻煩:C源文件往往包含很多頭文件,頭文件又包含其他頭文件,形成復雜的嵌套包含;C沒有嚴格限定源文件和頭文件的功能邊界,二者都可以包含全局變量和函數等實體定義。這都可能導致類型或實體定義被重復包含和展開,使編譯器拋出重定義錯誤。
解決重定義問題分三部分,多數人只知其一而不知其二和其三:
其一,用條件編譯(頭文件衛士)防止頭文件重復包含
假設源文件test.c中包含a.h和b.h兩個頭文件,而a.h和b.h裡又都包含另一個頭文件x.h(很常見),那麼x.h就會被test.c兩次include,如果x.h裡定義了某結構體,如:
typedef struct
{
……
}TEST
預處理(見C編譯過程)後,test.c裡包含兩個struct TEST定義,編譯器就會報重定義錯誤。一個巧妙辦法是套用下面頭文件模板(俗稱頭文件衛士):
#ifndef _HDRNAME_H //_HDRNAME_H按頭文件的文件名取名,防止同名沖突
#define _HDRNAME_H
…… (content of header file)
#endif
當頭文件第一次被包含,_HDRNAME_H還未define,#ifndef條件滿足,預處理器進入#ifndef和#endif之間,_HDRNAME_H被正式define,頭文件內容也得到處理。當再次被包含,由於_HDRNAME_H已定義,開頭的#ifndef不再滿足,頭文件內容被直接忽略。這樣防止因頭文件重復包含引起的類型重定義錯誤。這種做法基本算是C的江湖標准了。
其二,在C源文件裡定義全局變量與函數,不要在頭文件裡定義
#ifndef能防止頭文件重復包含導致的編譯階段類型重定義錯誤,卻無法防止頭文件中的全局變量和函數定義導致的鏈接階段實體重定義錯誤。例如:
/************main.c************/
#include "test.h"
void main()
{
test1();
test2();
}
/********** test.h**********/
#ifndef _TEST_H_
#define _TEST_H_
char str1[] = "char1";
char str2[] = "char2";
#endif
/*********test1.c***********/
#include "test.h"
extern char str1[];
void test1()
{
printf(str1);
}
/*********test2.c************/
#include "test.h"
extern char str2[];
void test2()
{
printf(str2);
}