1. 程式人生 > >如何防止標頭檔案被重複包含、巢狀包含

如何防止標頭檔案被重複包含、巢狀包含

【轉自】 http://hi.baidu.com/zengzhaonong/blog/item/8a8871062d481f7f03088106.html

#include檔案的一個不利之處在於一個頭檔案可能會被多次包含,為了說明這種錯誤,考慮下面的程式碼:

#include "x.h"
#include "x.h"

顯然,這裡檔案x.h被包含了兩次,沒有人會故意編寫這樣的程式碼。但是下面的程式碼:
#include "a.h"
#include "b.h"

看上去沒什麼問題。如果a.h和b.h都包含了一個頭檔案x.h。那麼x.h在此也同樣被包含了兩次,只不過它的形式不是那麼明顯而已。

多重包含在絕大多數情況下出現在大型程式中,它往往需要使用很多標頭檔案,因此要發現重複包含並不容易。要解決這個問題,我們可以使用條件編譯。如果所有的標頭檔案都像下面這樣編寫:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H

...//(標頭檔案內容)

#endif

那麼多重包含的危險就被消除了。當頭檔案第一次被包含時,它被正常處理,符號_HEADERNAME_H被定義為1。如果標頭檔案被再次包含,通過條件編譯,它的內容被忽略。符號_HEADERNAME_H按照被包含標頭檔案的檔名進行取名,以避免由於其他標頭檔案使用相同的符號而引起的衝突。

但是,你必須記住前處理器仍將整個標頭檔案讀入,即使這個標頭檔案所有內容將被忽略。由於這種處理將託慢編譯速度,所以如果可能,應該避免出現多重包含。

test-1.0使用#ifndef只是防止了標頭檔案被重複包含
(其實本例中只有一個頭件,不會存在重複包含的問題),但是無法防止變數被重複定義

# vi test.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern i;
extern void test1();
extern void test2();

int main()
{
   test1();
   printf("ok/n");
   test2();
   printf("%d/n",i);
   return 0;
}


# vi test.h
-------------------------------
#ifndef _TEST_H_

#define _TEST_H_

char add1[] = "www.shellbox.cn/n";
char add2[] = "www.scriptbox.cn/n";
int i = 10;
void test1();
void test2();

#endif



# vi test1.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern char add1[];

void test1()
{
   printf(add1);
}



# vi test2.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern char add2[];
extern i;

void test2()
{
   printf(add2);
   for (; i > 0; i--) 
       printf("%d-", i);
}



# Makefile
-------------------------------
test:    test.o test1.o test2.o
test1.o: test1.c
test2.o: test2.c
clean:
   rm test test.o test1.o test2.o


錯誤:
test-1.0編譯後會出現"multiple definition of"錯誤。

錯誤分析:
由於工程中的每個.c檔案都是獨立的解釋的,即使標頭檔案有
#ifndef _TEST_H_
#define _TEST_H_
....
#enfif
在其他檔案中只要包含了global.h就會獨立的解釋,然後每個.c檔案生成獨立的標示符。在編譯器連結時,就會將工程中所有的符號整合在一起,由於檔案中有重名變數,於是就出現了重複定義的錯誤。

解決方法
.c檔案中宣告變數,然後建一個頭檔案(.h檔案)在所有的變數宣告前加上extern,注意這裡不要對變數進行的初始化。然後在其他需要使用全域性變數的.c檔案中包含.h檔案。編譯器會為.c生成目標檔案,然後連結時,如果該.c檔案使用了全域性變數,連結器就會連結到此.c檔案 。






test-2.0

# vi test.c
-------------------------------
#include <stdio.h>
#include "test.h"

int i = 10;
char add1[] = "www.shellbox.cn/n";
char add2[] = "www.scriptbox.cn/n";
extern void test1();
extern void test2();

int main()
{
   test1();
   printf("ok/n");
   test2();
   printf("%d/n",i);
   return 0;
}


# vi test.h
-------------------------------
#ifndef _TEST_H_
#define _TEST_H_


extern i;
extern char add1[];
extern char add2[];


void test1();
void test2();

#endif



# vi test1.c
-------------------------------
#include <stdio.h>
#include "test.h"

void test1()
{
   printf(add1);
}


# vi test2.c
-------------------------------
#include <stdio.h>
#include "test.h"

void test2()
{
   printf(add2);
   for (; i > 0; i--) 
       printf("%d-", i);

}

 二、連結指示符:extern

如果希望呼叫其他程式設計語言(尤其是C)寫的函式,那麼,呼叫函式時必須告訴編譯器使用不同的要求.例如,當這樣的函式被呼叫時,函式名或引數排列的順序可能不同,無論是C++函式呼叫它,還是用其他語言寫的函式呼叫它.
    程式設計師用連結指示符(linkage directive)告訴編譯器,該函式是用其他的程式設計語言編寫的.

    連結指示符有兩種形式:
    單一語句(single statement)形式
    複合語句(compound statement)形式

    當複合語句連結指示符的括號中包含有#include時,在標頭檔案中的函式宣告都被假定是用連結指示符的程式設計語言所寫的.

    連結指示符不能出現在函式體中.

vi externC.cpp
-------------------------------------
#include <iostream>
extern "C" double sqrt(double);
int main()
{
    using std::cout;
    using std::endl;
    double result = sqrt(25);
    cout << "result = " << result << endl;
    return 0;
}

g++ externC.cpp

     如果我們希望C++函式能夠為C程式所用,我們也可以使用extern "C"連結指示符來使C++函式為C程式可用.






    作為一種面向物件的語言,C++支援函式過載,而過程式語言C則不支援。函式被C++編譯後在symbol庫中的名字與C語言的不同。例如,假設某個函式的原型為:
    void foo(int x, int y); 

    該函式被C編譯器編譯後在symbol庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字。_foo_int_int這樣的名字包含了函式名和函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。

    為了實現C和C++的混合程式設計,C++提供了C
連結交換指定符號extern "C"來解決名字匹配問題,函式宣告前加上extern "C"後,則編譯器就會按照C語言的方式將該函式編譯為_foo,這樣C語言中就可以呼叫C++的函數了。





cppExample.h
-----------------------------------
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
//被extern "C"限定的函式或變數首先是extern型別的;extern是C/C++語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。
//被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的;
extern "C" int add(int x, int y);
//extern int add(int x, int y);
#endif



cppExample.cpp
-----------------------------------
#include "cppExample.h"

int add( int x, int y )
{
    return x + y;
}



cFile.c
-----------------------------------
#include <stdio.h>
//這樣會編譯出錯
//#include "cppExample.h"

extern int add(int x, int y);

int main(int argc, char* argv[])
{
    printf("%d/n", add(2, 3));
    return 0;
}



-----------------------------------

gcc cFile.c cppExample.cpp

 三、變數定義與宣告的區別

 我們在程式設計中,時時刻刻都用到變數的定義和變數的宣告,可有些時候我們對這個概念不是很清楚,知道它是怎麼用,但卻不知是怎麼一會事,下面我就簡單的把他們的區別介紹如下:

    變數的宣告有兩種情況:
    (1) 一種是需要建立儲存空間的(定義、宣告)。例如:int a在宣告的時候就已經建立了儲存空間。 
    (2) 另一種是不需要建立儲存空間的(宣告)。例如:extern int a其中變數a是在別的檔案中定義的。
    前者是"定義性宣告(defining declaration)"或者稱為"定義(definition)",而後者是"引用性宣告(referncing declaration)"。從廣義的角度來講宣告中包含著定義,但是並非所有的宣告都是定義,例如:int a它既是宣告,同時又是定義。然而對於extern a來講它只是宣告不是定義。一般的情況下我們常常這樣敘述,把建立空間的宣告稱之為"定義",而把不需要建立儲存空間稱之為"宣告"。很明顯我們在這裡指的宣告是範圍比較窄的,也就是說非定義性質的宣告。

例如:在主函式中 
int main()
{
    extern int A; //這是個宣告而不是定義,宣告A是一個已經定義了的外部變數
                  //注意:宣告外部變數時可以把變數型別去掉如:extern A;
    dosth();      //執行函式
}

int A;            //是定義,定義了A為整型的外部變數(全域性變數) 


    外部變數(全域性變數)的"定義"與外部變數的"宣告"是不相同的,外部變數的定義只能有一次
,它的位置是在所有函式之外,而同一個檔案中的外部變數宣告可以是多次的,它可以在函式之內(哪個函式要用就在那個函式中宣告)也可以在函式之外(在外部變數的定義點之前)。系統會根據外部變數的定義(而不是根據外部變數的宣告)分配儲存空間的。對於外部變數來講,初始化只能是在"定義"中進行,而不是在"宣告"中。所謂的"宣告",其作用,是宣告該變數是一個已在後面定義過的外部變數,僅僅是在為了"提前"引用該變數而作的"宣告"而已。extern只作宣告,不作定義。 

    用static來宣告一個變數的作用有二:
    (1) 對於區域性變數用static宣告,則是為該變數分配的空間在整個程式的執行期內都始終存在
    (2) 外部變數用static來宣告,則該變數的作用只限於本檔案模組