一個大型的軟體項目通常包含很多複雜的功能,實現這個項目不是一個程式員單槍匹馬可以勝任的,往往需要一個團隊的有效分工合作,另外,在一個以C代碼為主的完整的項目中,經常也需要加入一些其他語言的代碼,例如,C代碼和彙編代碼的混合使用,C檔案和C++的同時使用。這些都增加了一個軟體項目的複雜程度,為了提高軟體品質,合理組織的各種代碼和檔案是非常重要的。組織代碼和檔案的目的是為了使團隊合作更加有效,使軟體項目有良好的可擴充性、可維護性、可移植性、可裁減、可測試性,防止錯誤發生,提高軟體的穩定性。
軟體項目通常採用層次化結構開發和模組化開發,例如,一個嵌入式軟體項目可能有驅動層,作業系統層,功能層,應用程式層,每一個層使用它的下層提供的介面,並為它的上層提供調用介面;模組則是每一個層中完成一個功能的單元,例如驅動層的每一個裝置的驅動就是一個模組,應用程式層的每個應用程式就是一個模組,模組使用下層提供的介面和同層其他模組提供的介面,完成特定功能,為上層和同層的其他模組提供調用介面。
這裡的介面是指一個功能模組暴露出來的,提供給其他模組的訪問具體功能的方法。根據C語言的特點,使用*.c檔案實現模組的功能,使用*.h檔案暴露單元的介面,在*.h檔案裡聲明外部其他模組可能是用的函數,資料類型,全域變數,類型定義,宏定義和常量定義.外部模組只需包含*.h檔案就可以使用相應的功能.當然,模組可以在細化為子模組.雖然我們這裡說的介面和COM(萬用群組件模型)裡定義的介面不同,但是,根據COM裡對介面的討論,為了使軟體在修改時,一個模組的修改不會影響到其他模組的一個模組的修改不會導致其他模組也需要修改,所以,介面第一次發布後,修改*.h檔案不能導致使用這個介面的其他模組需要重新編寫.
檔案組織的基本建議
- 使用層次化和模組化的軟體開發模型.每一個模組只能使用所在層和下一層模組提供的介面.
- 每個模組的檔案包存在獨立的一個檔案夾中.通常情況下,實現一個模組的檔案不止一個,這些相關的
檔案應該儲存在一個檔案夾中.
- 用於模組裁減的條件編譯宏儲存在一個獨立的檔案裡,便於軟體裁減。
- 硬體相關代碼和作業系統相關代碼與純C代碼相對獨立儲存,以便於軟體移植.
- 聲明和定義分開,使用*.h檔案暴露模組需要提供給外部的函數,宏,類型,常量,全域變數,盡量做到模組對外部透明,使用者在使用模組功能時不需要瞭解具體的實現,檔案一旦發布,要修改一定要很謹慎,
- 檔案夾和檔案命名要能夠反映出模組的功能,所以命名要用意義的名字。
- 正式版本和測試版本使用統一檔案,使用宏控制是否產生測試輸出。
- 必要的注釋不可缺少,提高檔案的可讀性。
標頭檔建議參考以下的規則
- 標頭檔中不能有可執行代碼,也不能有資料的定義,只能有宏、類型(typedef,struct,union,menu),資料和函數的聲明。
例如以下的代碼可以包含在標頭檔裡:
#define NAMESTRING “name”
typedef unsign long word;
menu
{
flag1;
flag2;
};
typedef struct
{
int x;
int y;
}Piont;
extent Fun(void);
extent int a;
全域變數和函數的定義不能出現在*.h檔案裡。例如下面的代碼不能包含在標頭檔:
int a;
void Fun1(void)
{
a++;
}
- 標頭檔中不能包本機資料(模組自己使用的資料或函數,不被其他模組使用)。這一點相當於物件導向程式設計裡的私人成員,即只有模組自己使用的函數,資料,不要用extent在標頭檔裡聲明,只有模組自己使用的宏,常量,類型也不要在標頭檔裡聲明,應該在自己的*.c檔案裡聲明。
- 含一些需要使用的聲明。在標頭檔裡聲明外部需要使用的資料,函數,宏,類型。
- 防止被重複包含。使用下面的宏防止一個標頭檔被重複包含。
#ifndef MY_INCLUDE_H
#define MY_INCLUDE_H
<標頭檔內容 >
#endif
- 包含extern "C",使的程式可以在C++編譯器被編譯
#ifdef __cplusplus
extern "C"{
#endif
<函式宣告 >
#ifdef __cplusplus
}
#enfif
被extern "C"修飾的變數和函數是按照C語言方式編譯和串連的;未加extern“C”聲明時的編譯方式,作為一種物件導向的語言,C++支援函數重載,而過程式語言C則不支援。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:
void foo(int x,int y);該函數被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能產生的名字不同,但是都採用了相同的機制,產生的新名字稱為“mangled name”)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型資訊,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void
foo(int x,int y)與void foo(int x,float y)編譯產生的符號是不相同的,後者為_foo_int_float。 同樣地,C++中的變數除支援局部變數外,還支援類成員變數和全域變數。使用者所編寫程式的類成員變數可能與全域變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時間,與函數的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域變數名字不同。加extern "C"聲明後的編譯和串連,強制C++連接器按照C編譯器產生的符號_foo連結。
結合起來就是:
#ifndef MY_INCLUDE_H #define MY_INCLUDE_H #ifdef __cplusplus extern "C"{ #endif <函式宣告 > #ifdef __cplusplus } #enfif #endif
- 保證在使用這個標頭檔時,使用者不用再包含使用此標頭檔的其他前提標頭檔,即要使用的標頭檔已經包含在此標頭檔裡。例如:area.h標頭檔包含了面積相關的操作,要使用這個標頭檔不需同時包含了關於點操作的標頭檔piont.h。使用者在使用area.h時不需要手動包含piont.h,因為我們已經在 area.h中用#include
“point.h”包含了這個標頭檔。
用來暴露介面的標頭檔還需要參考更多的規則:
1,一個模組一個介面,不能幾個模組用一個介面。
2,檔案名稱為和實現模組的c檔案相同。abc.c--abc.h
3,盡量不要使用extern來聲明一些共用的資料。因為這種做法是不安全的,外部其他模組的使用者可能不能完全理解這些變數的含義,最好提供函數GetPut訪問這些變數。
4,盡量避免包含其他的標頭檔,除非這些標頭檔是獨立存在的。這一點的意思是,在作為介面的標頭檔中,盡量不要包含其他模組的那些暴露*.C檔案中內容的標頭檔,但是可以包好一些不是用來暴露介面的標頭檔。
5,不要包含那些只有在可執行檔中才使用的標頭檔,這些標頭檔應該在*.c檔案中包含。這一點如同上一點,為了提高介面的獨立性和透明度。
6,介面檔案要有面向使用者的充足的注釋。從應用角度描述個暴露的內容。
7,介面檔案在發布後盡量避免修改,即使修改也要保證不影響使用者程式。
多個代碼檔案使用一個介面檔案:這種標頭檔用於那些認為一個模組使用一個檔案太大的情況。增加以下建議。
1,多個代碼檔案組成的一個模組只有一個介面檔案。因為這些檔案完成的是一個模組。
2,使用模組下檔案命名 <系統名 > <模組名命名>
3,不要濫用這種檔案。
4,有時候也會出現幾個*.c檔案用於共向資料的*.h檔案,這種檔案的特點是在一個*.c檔案裡定義全域變數,而在其他*.c檔案裡使用,要將這種檔案和用於暴露模組介面的檔案區別。
5,一個模組如果有幾個子模組,可以用一個*.h檔案暴露介面,在這個檔案裡用#include包含每個子模組的介面檔案。
還有一種標頭檔,說明性標頭檔,這種標頭檔不需要有一個對應的代碼檔案,在這種檔案裡大多包含了大量的宏定義,沒有暴露的資料變數和函數。這些檔案給出以下建議:
1,包含一些需要的概念性的東西.
2,命名方式,定義的功能.h
3,不包含任何其他的標頭檔.
4,不定義任何類型.
5,不包含任何資料和函式宣告.
上面介紹了C標頭檔的一些建議,下面介紹C代碼檔案*.c檔案的一些建議,*.c檔案是C語言中產生彙編代碼和機器碼的內容,要注意以下建議:
1.命名方式 模組名.c
2,用static修飾本地的資料和函數。
3,不要使用external。這是在*.h中使用的,可以被包含進來。
4,無論什麼時候定義內部的對象,確保獨立與其他執行檔案。
5,這個檔案裡必須包含相應功能函數。