在C語言的應(yīng)用領(lǐng)域,如通訊領(lǐng)域和嵌入式系統(tǒng)領(lǐng)域,一個的軟件項目通常包含很多復(fù)雜的功能,實現(xiàn)這個項目不 是一個程序員單槍匹馬可以勝任的,往往需要一個團(tuán)隊的有效合作,另外,在一個以C代碼為主的完整的項目中,經(jīng)常也需要加入一些其他語言的代碼,例如,C代 碼和匯編代碼的混合使用,C文件和C++的同時使用。這些都增加了一個軟件項目的復(fù)雜程度,為了提高軟件質(zhì)量,合理組織的各種代碼和文件是非常重要的。
組織代碼和文件的目的是為了使團(tuán)隊合作更加有效,使軟件項目有良好的可擴(kuò)展性、可維護(hù)性、可移植性、可裁減、可測試性,防止錯誤發(fā)生,提高軟件的穩(wěn)定性。 通常情況下,軟件項目采用層次化結(jié)構(gòu)和模塊化開發(fā)的方法,例如,一個嵌入式軟件項目可能有驅(qū)動層,操作系統(tǒng)層,功能層,應(yīng)用程序?qū)?,每一個層使用它的下層 提供的接口,并為它的上層提供調(diào)用接口,模塊則是每一個層中完成一個功能的單元,例如驅(qū)動層的每一個設(shè)備的驅(qū)動就是一個模塊,應(yīng)用層的每個應(yīng)用程序就是一 個模塊,模塊使用下層提供的接口和同層其他模塊提供的接口,完成特定功能,為上層和同層的其他模塊提供調(diào)用接口。
這里的接口是指一個功能模塊暴露出來的,提供給其他模塊的訪問具體功能的方法。根據(jù)C語言的特點,使用*.c文件實現(xiàn)模塊的功能,使用*.h文件暴露單元 的接口,在*.h文件里聲明外部其他模塊可能是用的函數(shù),數(shù)據(jù)類型,全局變量,類型定義,宏定義和常量定義。外部模塊只需包含*.h文件就可以使用相應(yīng)的 功能。當(dāng)然,模塊可以在細(xì)化為子模塊。雖然我們這里說的接口和COM(通用組件模型)里定義的接口不同,但是,根據(jù)COM里對接口的討論,為了使軟件在修 改時,一個模塊的修改不會影響到其他模塊的一個模塊的修改不會導(dǎo)致其他模塊也需要修改,所以,接口第一次發(fā)布后,修改*.h文件不能導(dǎo)致使用這個接口的其 他模塊需要重新編寫。
根據(jù)C語言的特點,并借鑒一些成熟軟件項目代碼,總結(jié)C項目中代碼文件組織的基本建議:
1,使用層次化和模塊化的軟件開發(fā)模型。每一個模塊只能使用所在層和下一層模塊提供的接口。
2,每個模塊的文件包存在獨(dú)立的一個文件夾中。通常情況下,實現(xiàn)一個模塊的文件不止一個,這些相關(guān)的文件應(yīng)該保存在一個文件夾中。
3,用于模塊裁減的條件編譯宏保存在一個獨(dú)立的文件里,便于軟件裁減。
4,硬件相關(guān)代碼和操作系統(tǒng)相關(guān)代碼與純C代碼相對獨(dú)立保存,以便于軟件移植。
5,聲明和定義分開,使用*.h文件暴露模塊需要提供給外部的函數(shù),宏,類型,常量,全局變量,盡量做到模塊對外部透明,用戶在使用模塊功能時不需要了解具體的實現(xiàn),文件一旦發(fā)布,要修改一定要很慎重,
6,文件夾和文件命名要能夠反映出模塊的功能。
7,正式版本和測試版本使用統(tǒng)一文件,使用宏控制是否產(chǎn)生測試輸出。
8,必要的注釋不可缺少。
理想的情況下,一個可執(zhí)行的模塊提供一個公開的接口,即使用一個*.h文件暴露接口,但是,有時候,一個模塊需要提供不止一個接口,這時,就要為每個定義 的接口提供一個公開的接口。在C語言的里,每個C文件是一個模塊,頭文件為使用這個模塊的用戶提供接口,用戶只要包含相應(yīng)的頭文件就可以使用在這個頭文件 中暴露的接口。所有的頭文件都建議參考以下的規(guī)則:
1, 頭文件中不能有可執(zhí)行代碼,也不能有數(shù)據(jù)的定義,只能有宏、類型(typedef,struct,union,menu),數(shù)據(jù)和函數(shù)的聲明。例如以下的代碼可以包含在頭文件里:
#define NAMESTRING “name”
typedef unsign long word;
menu{
flag1;
flag2;
};
typedef struct{
int x;
int y;
} Piont;
extent Fun(void);
extent int a;
全局變量和函數(shù)的定義不能出現(xiàn)在*.h文件里。例如下面的代碼不能包含在頭文件:
int a;
void Fun1(void)
{
a++;
}
2,頭文件中不能包本地數(shù)據(jù)(模塊自己使用的數(shù)據(jù)或函數(shù),不被其他模塊使用)。這一點相當(dāng)于面向?qū)ο蟪绦蛟O(shè)計里的私有成員,即只有模塊自己使用的函數(shù),數(shù) 據(jù),不要用extent在頭文件里聲明,只有模塊自己使用的宏,常量,類型也不要在頭文件里聲明,應(yīng)該在自己的*.c文件里聲明。
3,含一些需要使用的聲明。在頭文件里聲明外部需要使用的數(shù)據(jù),函數(shù),宏,類型。
4,防止被重復(fù)包含。使用下面的宏防止一個頭文件被重復(fù)包含。
#ifndef MY_INCLUDE_H
#define MY_INCLUDE_H
<頭文件內(nèi)容>
#endif
5,包含extern “C”,使的程序可以在C++編譯器被編譯
#ifdef __cplusplus
extern “C”{
#endif
<函數(shù)聲明>
#ifdef __cplusplus
}
#enfif
被extern “C”修飾的變量和函數(shù)是按照C語言方式編譯和連接的;未加extern “C”聲明時的編譯方式,作為一種面向?qū)ο蟮恼Z言,C++支持函數(shù)重載,而過程式語言C則不支持。函數(shù)被C++編譯后在符號庫中的名字與C語言的不同。例如,假設(shè)某個函數(shù)的原型為:
void foo( int x, int y );該函數(shù)被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產(chǎn)生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機(jī)制,生成的新名字稱為“mangled name”)。_foo_int_int這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類型信息,C++就是靠這種機(jī)制來實現(xiàn)函數(shù)重載的。例如,在C++中,函數(shù)void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,后者為_foo_int_float?!⊥瑯拥?,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫 程序的類成員變量可能與全局變量同名,我們以”.”來區(qū)分。而本質(zhì)上,編譯器在進(jìn)行編譯時,與函數(shù)的處理相似,也為類中的變量取了一個獨(dú)一無二的名字,這 個名字與用戶程序中同名的全局變量名字不同。加extern “C”聲明后的編譯和連接,強(qiáng)制C++連接器按照C編譯器產(chǎn)生的符號_foo鏈接。
6,保證在使用這個頭文件時,用戶不用再包含使用此頭文件的其他前提頭文件,即要使用的頭文件已經(jīng)包含在此頭文件里。例如:area.h頭文件包含了面積 相關(guān)的操作,要使用這個頭文件不需同時包含了關(guān)于點操作的頭文件piont.h。用戶在使用area.h時不需要手動包含piont.h,因為我們已經(jīng)在 area.h中用#include “point.h”包含了這個頭文件。
有一些頭文件是為用戶提供調(diào)用接口,這種頭文件中聲明了模塊中需要給其他模塊使用的函數(shù)和數(shù)據(jù),鑒于軟件質(zhì)量上的考慮,處理參考以上的規(guī)則,用來暴露接口的頭文件還需要參考更多的規(guī)則:
1,一個模塊一個接口,不能幾個模塊用一個接口。
2,文件名為和實現(xiàn)模塊的c文件相同。abc.c–abc.h
3,盡量不要使用extern來聲明一些共享的數(shù)據(jù)。因為這種做法是不安全的,外部其他模塊的用戶可能不能完全理解這些變量的含義,最好提供函數(shù)訪問這些變量。
4,盡量避免包含其他的頭文件,除非這些頭文件是獨(dú)立存在的。這一點的意思是,在作為接口的頭文件中,盡量不要包含其他模塊的那些暴露*.C文件中內(nèi)容的頭文件,但是可以包好一些不是用來暴露接口的頭文件。
5,不要包含那些只有在可執(zhí)行文件中才使用的頭文件,這些頭文件應(yīng)該在*.c文件中包含。這一點如同上一點,為了提高接口的獨(dú)立性和透明度。
6,接口文件要有面向用戶的充足的注釋。從應(yīng)用角度描述個暴露的內(nèi)容。
7,接口文件在發(fā)布后盡量避免修改,即使修改也要保證不影響用戶程序。
多個代碼文件使用一個接口文件:這種頭文件用于那些認(rèn)為一個模塊使用一個文件太大的情況。對于這種情況對于這種情況在參考上述建議后,也要參考以下建議。
1,多個代碼文件組成的一個模塊只有一個接口文件。因為這些文件完成的是一個模塊。
2,使用模塊下文件命名<系統(tǒng)名><模塊名命名>
3,不要濫用這種文件。
4,有時候也會出現(xiàn)幾個*.c文件用于共向數(shù)據(jù)的*.h文件,這種文件的特點是在一個*.c文件里定義全局變量,而在其他*.c文件里使用,要將這種文件和用于暴露模塊接口的文件區(qū)別。
5,一個模塊如果有幾個子模塊,可以用一個*.h文件暴露接口,在這個文件里用#include包含每個子模塊的接口文件。
還有一種頭文件,說明性頭文件,這種頭文件不需要有一個對應(yīng)的代碼文件,在這種文件里大多包含了大量的宏定義,沒有暴露的數(shù)據(jù)變量和函數(shù)。這些文件給出以下建議:
1,包含一些需要的概念性的東西。
2,命名方式,定義的功能。h
3,不包含任何其他的頭文件。
4,不定義任何類型。
5,不包含任何數(shù)據(jù)和函數(shù)聲明。
上面介紹了C頭文件的一些建議,下面介紹C代碼文件*.c文件的一些建議,*.c文件是C語言中生成匯編代碼和機(jī)器碼的內(nèi)容,要注意以下建議:
1.命名方式 模塊名。c
2,用static修飾本地的數(shù)據(jù)和函數(shù)。
3,不要使用external。這是在*.h中使用的,可以被包含進(jìn)來。
4,無論什么時候定義內(nèi)部的對象,確保獨(dú)立與其他執(zhí)行文件。
5,這個文件里必須包含相應(yīng)功能函數(shù)。
上面介紹了一些C文件組織的建議,用于提高C語言項目的質(zhì)量,在以后的C項目組織中,學(xué)習(xí)面向?qū)ο蠛虲OM的思想,將這些思想加入到C程序中,能夠?qū)懗龈?高質(zhì)量的代碼。上面的建議在具體的項目里應(yīng)該靈活運(yùn)用,附錄里有*.h頭文件和*.c代碼文件的模版,在工程中可以作為參考。另外,C工程中經(jīng)常有一些匯 編代碼文件,這些文件也要使有*.h頭文件暴露其中的數(shù)據(jù)和函數(shù),以便其他*.c文件包含使用。