1. 程式人生 > >大型專案中C語言的模組化建議

大型專案中C語言的模組化建議

一個大型的軟體專案通常包含很多複雜的功能,實現這個專案不是一個程式設計師單槍匹馬可以勝任的,往往需要一個團隊的有效分工合作,另外,在一個以C程式碼為主的完整的專案中,經常也需要加入一些其他語言的程式碼,例如,C程式碼和彙編程式碼的混合使用,C檔案和C++的同時使用。這些都增加了一個軟體專案的複雜程度,為了提高軟體質量,合理組織的各種程式碼和檔案是非常重要的組織程式碼和檔案的目的是為了使團隊合作更加有效,使軟體專案有良好的可擴充套件性、可維護性、可移植性、可裁減、可測試性,防止錯誤發生,提高軟體的穩定性。

軟體專案通常採用層次化結構開發和模組化開發,例如,一個嵌入式軟體專案可能有驅動層,作業系統層,功能層,應用程式層,每一個層使用它的下層提供的介面,併為它的上層提供呼叫介面

模組則是每一個層中完成一個功能的單元,例如驅動層的每一個裝置的驅動就是一個模組,應用層的每個應用程式就是一個模組,模組使用下層提供的介面和同層其他模組提供的介面,完成特定功能,為上層和同層的其他模組提供呼叫介面。

這裡的介面是指一個功能模組暴露出來的,提供給其他模組的訪問具體功能的方法。根據C語言的特點,使用*.c檔案實現模組的功能,使用*.h檔案暴露單元的介面,在*.h檔案裡宣告外部其他模組可能是用的函式,資料型別,全域性變數,型別定義,巨集定義和常量定義.外部模組只需包含*.h檔案就可以使用相應的功能.當然,模組可以在細化為子模組.雖然我們這裡說的介面和COM(通用元件模型)裡定義的介面不同,但是,根據COM裡對介面的討論,為了使軟體在修改時,一個模組的修改不會影響到其他模組的一個模組的修改不會導致其他模組也需要修改,所以,介面第一次釋出後,修改*.h檔案不能導致使用這個介面的其他模組需要重新編寫.    

  檔案組織的基本建議     

  • 使用層次化和模組化的軟體開發模型.每一個模組只能使用所在層和下一層模組提供的介面.      
  • 每個模組的檔案包存在獨立的一個資料夾中.通常情況下,實現一個模組的檔案不止一個,這些相關的檔案應該儲存在一個資料夾中.      
  • 用於模組裁減的條件編譯巨集儲存在一個獨立的檔案裡,便於軟體裁減
  • 硬體相關程式碼和作業系統相關程式碼與純C程式碼相對獨立儲存,以便於軟體移植.      
  • 宣告和定義分開,使用*.h檔案暴露模組需要提供給外部的函式,巨集,型別,常量,全域性變數,儘量做到模組對外部透明,使用者在使用模組功能時不需要了解具體的實現,檔案一旦釋出,要修改一定要很慎重,      
  • 資料夾和檔案命名要能夠反映出模組的功能,所以命名要用意義的名字。
  • 正式版本和測試版本使用統一檔案,使用巨集控制是否產生測試輸出。      
  • 必要的註釋不可缺少,提高檔案的可讀性。

  標頭檔案建議參考以下的規則

  1. 標頭檔案中不能有可執行程式碼,也不能有資料的定義,只能有巨集、型別(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++;      

    }    

  1. 標頭檔案中不能包本地資料(模組自己使用的資料或函式,不被其他模組使用)。這一點相當於面向物件程式設計裡的私有成員,即只有模組自己使用的函式,資料,不要用extent在標頭檔案裡宣告,只有模組自己使用的巨集,常量,型別也不要在標頭檔案裡宣告,應該在自己的*.c檔案裡宣告。      
  1. 含一些需要使用的宣告。在標頭檔案裡宣告外部需要使用的資料,函式,巨集,型別。
  1. 防止被重複包含。使用下面的巨集防止一個頭檔案被重複包含。      

    #ifndef       MY_INCLUDE_H      

    #define       MY_INCLUDE_H      

<標頭檔案內容 >      

    #endif      

  1. 包含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      

  1. 保證在使用這個標頭檔案時,使用者不用再包含使用此標頭檔案的其他前提標頭檔案,即要使用的標頭檔案已經包含在此標頭檔案裡。例如: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,這個檔案裡必須包含相應功能函式。