c函式編寫規範

helloxchen發表於2010-11-23

一、可重入函式

1)什麼是可重入性?

可重入(reentrant)函式可以由多於一個任務併發使用,而不必擔心資料錯誤。相

反, 不可重入(non-reentrant)函式不能由超過一個任務所共享,除非能確保函式

的互斥(或者使用訊號量,或者在程式碼的關鍵部分禁用中斷)。可重入函式可以在任

意時刻被中斷,稍後再繼續執行,不會丟失資料。可重入函式要麼使用本地變數,要

麼在使用全域性變數時保護自己的資料。

2)可重入函式:

不為連續的呼叫持有靜態資料。

不返回指向靜態資料的指標;所有資料都由函式的呼叫者提供。

使用本地資料,或者透過製作全域性資料的本地複製來保護全域性資料。

如果必須訪問全域性變數,記住利用互斥訊號量來保護全域性變數。

絕不呼叫任何不可重入函式。

3)不可重入函式:

函式中使用了靜態變數,無論是全域性靜態變數還是區域性靜態變數。

函式返回靜態變數。

函式中呼叫了不可重入函式。

函式體內使用了靜態的資料結構;

函式體內呼叫了malloc()或者free()函式;

函式體內呼叫了其他標準I/O函式。

函式是singleton中的成員函式而且使用了不使用執行緒獨立儲存的成員變數 。

總的來說,如果一個函式在重入條件下使用了未受保護的共享的資源,那麼它是不可

重入的。

4)示例

在多執行緒條件下,函式應當是執行緒安全的,進一步,更強的條件是可重入的。可重入

函式保證了在多執行緒條件下,函式的狀態不會出現錯誤。以下分別是一個不可重入和

可重入函式的示例:

//c code

static int tmp;

void func1(int* x, int* y) {

tmp=*x;

*x=*y;

*y=tmp;

}

void func2(int* x, int* y) {

int tmp;

tmp=*x;

*x=*y;

*y=tmp;

}

func1是不可重入的,func2是可重入的。因為在多執行緒條件下,作業系統會在func1

還沒有執行完的情況下,切換到另一個執行緒中,那個執行緒可能再次呼叫func1,這樣

狀態就錯了。

二、函式編寫規範

1 :對所呼叫函式的錯誤返回碼要仔細、全面地處理

2 :明確函式功能,精確(而不是近似)地實現函式設計

3 :編寫可重入函式時,應注意區域性變數的使用(如編寫C/C++ 語言的可重入函式時

,應使用auto 即預設態區域性變數或暫存器變數)

說明:編寫C/C++語言的可重入函式時,不應使用static區域性變數,否則必須經過特

殊處理,才能使函式具有可重入性。

4 :編寫可重入函式時,若使用全域性變數,則應透過關中斷、訊號量(即P 、V 操作

)等手段對其加以保護

說明:若對所使用的全域性變數不加以保護,則此函式就不具有可重入性,即當多個進

程呼叫此函式時,很有可能使有關全域性變數變為不可知狀態。

示例:假設Exam是int型全域性變數,函式Squre_Exam返回Exam平方值。那麼如下函式

不具有可重入性。

unsigned int example( int para )

{

unsigned int temp;

Exam = para; // (**)

temp = Square_Exam( );

return temp;

}

此函式若被多個程式呼叫的話,其結果可能是未知的,因為當(**)語句剛執行完後

,另外一個使用本函式的程式可能正好被啟用,那麼當新啟用的程式執行到此函式時

,將使Exam賦與另一個不同的para值,所以當控制重新回到"temp = Square_Exam(

)"後,計算出的temp很可能不是預想中的結果。此函式應如下改進。

unsigned int example( int para )

{

unsigned int temp;

[申請訊號量操作] // 若申請不到"訊號量",說明另外的程式正處於

Exam = para; // 給Exam賦值並計算其平方過程中(即正在使用此

temp = Square_Exam( ); // 訊號),本程式必須等待其釋放訊號後,才可繼

[釋放訊號量操作] // 續執行。若申請到訊號,則可繼續執行,但其

// 它程式必須等待本程式釋放訊號量後,才能再使

// 用本訊號。

return temp;

}

5 :在同一專案組應明確規定對介面函式引數的合法性檢查應由函式的呼叫者負責還

是由介面函式本身負責,預設是由函式呼叫者負責

說明:對於模組間介面函式的引數的合法性檢查這一問題,往往有兩個極端現象,即

:要麼是呼叫者和被呼叫者對引數均不作合法性檢查,結果就遺漏了合法性檢查這一

必要的處理過程,造成問題隱患;要麼就是呼叫者和被呼叫者均對引數進行合法性檢

查,這種情況雖不會造成問題,但產生了冗餘程式碼,降低了效率。

6 :防止將函式的引數作為工作變數

說明:將函式的引數作為工作變數,有可能錯誤地改變引數內容,所以很危險。對必

須改變的引數,最好先用區域性變數代之,最後再將該區域性變數的內容賦給該引數。

示例:如下函式的實現就不太好。

void sum_data( unsigned int num, int *data, int *sum )

{

unsigned int count;

*sum = 0;

for (count = 0; count < num; count++)

{

*sum += data[count]; // sum成了工作變數,不太好。

}

}

若改為如下,則更好些。

void sum_data( unsigned int num, int *data, int *sum )

{

unsigned int count ;

int sum_temp;

sum_temp = 0;

for (count = 0; count < num; count ++)

{

sum_temp += data[count];

}

*sum = sum_temp;

}

7 :函式的規模儘量限制在200 行以內

說明:不包括註釋和空格行。

8 :一個函式僅完成一件功能

9 :為簡單功能編寫函式

說明:雖然為僅用一兩行就可完成的功能去編函式好象沒有必要,但用函式可使功能

明確化,增加程式可讀性,亦可方便維護、測試。

示例:如下語句的功能不很明顯。

value = ( a > b ) ? a : b ;

改為如下就很清晰了。

int max (int a, int b)

{

return ((a > b) ? a : b);

}

value = max (a, b);

或改為如下。

#define MAX (a, b) (((a) > (b)) ? (a) : (b))

value = MAX (a, b);

10:不要設計多用途面面俱到的函式

說明:多功能集於一身的函式,很可能使函式的理解、測試、維護等變得困難。

11:函式的功能應該是可以預測的,也就是隻要輸入資料相同就應產生同樣的輸出

說明:帶有內部"儲存器"的函式的功能可能是不可預測的,因為它的輸出可能取決於

內部儲存器(如某標記)的狀態。這樣的函式既不易於理解又不利於測試和維護。在

C/C++語言中,函式的static區域性變數是函式的內部儲存器,有可能使函式的功能不

可預測,然而,當某函式的返回值為指標型別時,則必須是STATIC的區域性變數的地址

作為返回值,若為AUTO類,則返回為錯針。

示例:如下函式,其返回值(即功能)是不可預測的。

unsigned int integer_sum( unsigned int base )

{

unsigned int index;

static unsigned int sum = 0; // 注意,是static型別的。

// 若改為auto型別,則函式即變為可預測。

for (index = 1; index <= base; index++)

{

sum += index;

}

return sum;

}

12 :儘量不要編寫依賴於其他函式內部實現的函式

說明:此條為函式獨立性的基本要求。由於目前大部分高階語言都是結構化的,所以

透過具體語言的語法要求與編譯器功能,基本就可以防止這種情況發生。但在彙編語

言中,由於其靈活性,很可能使函式出現這種情況。

示例:如下是在DOS下TASM的彙編程式例子。過程Print_Msg的實現依賴於Input_Msg

的具體實現,這種程式是非結構化的,難以維護、修改。

... // 程式程式碼

proc Print_Msg // 過程(函式)Print_Msg

... // 程式程式碼

jmp LABEL

... // 程式程式碼

endp

proc Input_Msg // 過程(函式)Input_Msg

... // 程式程式碼

LABEL:

... // 程式程式碼

endp

13 :避免設計多引數函式,不使用的引數從介面中去掉

說明:目的減少函式間介面的複雜度。

14 :非排程函式應減少或防止控制引數,儘量只使用資料引數

說明:本建議目的是防止函式間的控制耦合。排程函式是指根據輸入的訊息型別或控

制命令,來啟動相應的功能實體(即函式或過程),而本身並不完成具體功能。控制

引數是指改變函式功能行為的引數,即函式要根據此引數來決定具體怎樣工作。非調

度函式的控制引數增加了函式間的控制耦合,很可能使函式間的耦合度增大,並使函

數的功能不唯一。

示例:如下函式構造不太合理。

int add_sub( int a, int b, unsigned char add_sub_flg )

{

if (add_sub_flg == INTEGER_ADD)

{

return (a + b);

}

else

{

return (a b);

}

}

不如分為如下兩個函式清晰。

int add( int a, int b )

{

return (a + b);

}

int sub( int a, int b )

{

return (a b);

}

15 :檢查函式所有引數輸入的有效性

16 :檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等

說明:函式的輸入主要有兩種:一種是引數輸入;另一種是全域性變數、資料檔案的輸

入,即非引數輸入。函式在使用輸入之前,應進行必要的檢查。

17 :函式名應準確描述函式的功能

18 :使用動賓片語為執行某操作的函式命名。如果是OOP 方法,可以只有動詞(名

詞是物件本身)

示例:參照如下方式命名函式。

void print_record( unsigned int rec_ind ) ;

int input_record( void ) ;

unsigned char get_current_color( void ) ;

19 :避免使用無意義或含義不清的動詞為函式命名

說明:避免用含義不清的動詞如process、handle等為函式命名,因為這些動詞並沒

有說明要具體做什麼。

20 :函式的返回值要清楚、明瞭,讓使用者不容易忽視錯誤情況

說明:函式的每種出錯返回值的意義要清晰、明瞭、準確,防止使用者誤用、理解錯

誤或忽視錯誤返回碼。

21 :除非必要,最好不要把與函式返回值型別不同的變數,以編譯系統預設的轉換

方式或強制的轉換方式作為返回值返回

22 :讓函式在呼叫點顯得易懂、容易理解

23 :在呼叫函式填寫引數時,應儘量減少沒有必要的預設資料型別轉換或強制資料

型別轉換

說明:因為資料型別轉換或多或少存在危險。

24 :避免函式中不必要語句,防止程式中的垃圾程式碼

說明:程式中的垃圾程式碼不僅佔用額外的空間,而且還常常影響程式的功能與效能,

很可能給程式的測試、維護等造成不必要的麻煩。

25 :防止把沒有關聯的語句放到一個函式中

說明:防止函式或過程內出現隨機內聚。隨機內聚是指將沒有關聯或關聯很弱的語句

放到同一個函式或過程中。隨機內聚給函式或過程的維護、測試及以後的升級等造成

了不便,同時也使函式或過程的功能不明確。使用隨機內聚函式,常常容易出現在一

種應用場合需要改進此函式,而另一種應用場合又不允許這種改進,從而陷入困境。

在程式設計時,經常遇到在不同函式中使用相同的程式碼,許多開發人員都願把這些程式碼提

出來,並構成一個新函式。若這些程式碼關聯較大並且是完成一個功能的,那麼這種構

造是合理的,否則這種構造將產生隨機內聚的函式。

示例:如下函式就是一種隨機內聚。

void Init_Var( void )

{

Rect.length = 0;

Rect.width = 0; /* 初始化矩形的長與寬 */

Point.x = 10;

Point.y = 10; /* 初始化"點"的座標 */

}

矩形的長、寬與點的座標基本沒有任何關係,故

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1041924/,如需轉載,請註明出處,否則將追究法律責任。

相關文章