天天看點

程式設計修養(三)

6、if 語句對出錯的處理

———————————

我看見你說了,這有什麼好說的。還是先看一段程式代碼吧。

    if ( ch >= '0' && ch <= '9' ){

        /* 正常處理代碼 */

    }else{

        /* 輸出錯誤資訊 */

        printf("error ......\n");

        return ( FALSE );

    }

這種結構很不好,特别是如果“正常處理代碼”很長時,對于這種情況,最好不要用else。先判斷錯誤,如:

    if ( ch < '0' || ch > '9' ){

    /* 正常處理代碼 */

    ......

這樣的結構,不是很清楚嗎?突出了錯誤的條件,讓别人在使用你的函數的時候,第一眼就能看到不合法的條件,于是就會更下意識的避免。

7、頭檔案中的#ifndef

——————————

千萬不要忽略了頭件的中的#ifndef,這是一個很關鍵的東西。比如你有兩個C檔案,這兩個C檔案都include了同一個頭檔案。而編譯時,這兩個C檔案要一同編譯成一個可運作檔案,于是問題來了,大量的聲明沖突。

還是把頭檔案的内容都放在#ifndef和#endif中吧。不管你的頭檔案會不會被多個檔案引用,你都要加上這個。一般格式是這樣的:

    #ifndef  <辨別>

    #define <辨別>

    #endif

<辨別>在理論上來說可以是自由命名的,但每個頭檔案的這個“辨別”都應該是唯一的。辨別的命名規則一般是頭檔案名全大寫,前後加下劃線,并把檔案名中的“.”也變成下劃線,如:stdio.h

    #ifndef _STDIO_H_

    #define _STDIO_H_

(BTW:預編譯有多很有用的功能。你會用預編譯嗎?)    

8、在堆上配置設定記憶體

—————————

可能許多人對記憶體配置設定上的“棧 stack”和“堆 heap”還不是很明白。包括一些科班出身的人也不明白這兩個概念。我不想過多的說這兩個東西。簡單的來講,stack上配置設定的記憶體系統自動釋放,heap上配置設定的記憶體,系統不釋放,哪怕程式退出,那一塊記憶體還是在那裡。stack一般是靜态配置設定記憶體,heap上一般是動态配置設定記憶體。

由malloc系統函數配置設定的記憶體就是從堆上配置設定記憶體。從堆上配置設定的記憶體一定要自己釋放。用free釋放,不然就是術語——“記憶體洩露”(或是“記憶體漏洞”)—— Memory Leak。于是,系統的可配置設定記憶體會随malloc越來越少,直到系統崩潰。還是來看看“棧記憶體”和“堆記憶體”的差别吧。

    棧記憶體配置設定

    —————

    char*

    AllocStrFromStack()

    {

        char pstr[100];

        return pstr;

    堆記憶體配置設定

    AllocStrFromHeap(int len)

        char *pstr;

        if ( len <= 0 ) return NULL;

        return ( char* ) malloc( len );

對于第一個函數,那塊pstr的記憶體在函數傳回時就被系統釋放了。于是所傳回的char*什麼也沒有。而對于第二個函數,是從堆上配置設定記憶體,是以哪怕是程式退出時,也不釋放,是以第二個函數的傳回的記憶體沒有問題,可以被使用。但一定要調用free釋放,不然就是Memory Leak!

在堆上配置設定記憶體很容易造成記憶體洩漏,這是C/C++的最大的“克星”,如果你的程式要穩定,那麼就不要出現Memory Leak。是以,我還是要在這裡千叮咛萬囑付,在使用malloc系統函數(包括calloc,realloc)時千萬要小心。

記得有一個UNIX上的服務應用程式,大約有幾百的C檔案編譯而成,運作測試良好,等使用時,每隔三個月系統就是down一次,搞得許多人焦頭爛額,查不出問題所在。隻好,每隔兩個月人工手動重新開機系統一次。出現這種問題就是Memery Leak在做怪了,在C/C++中這種問題總是會發生,是以你一定要小心。一個Rational的檢測工作——Purify,可以幫你測試你的程式有沒有記憶體洩漏。

我保證,做過許多C/C++的工程的程式員,都會對malloc或是new有些感冒。當你什麼時候在使用malloc和new時,有一種輕度的緊張和惶恐的感覺時,你就具備了這方面的修養了。

對于malloc和free的操作有以下規則:

1) 配對使用,有一個malloc,就應該有一個free。(C++中對應為new和delete)

2) 盡量在同一層上使用,不要像上面那種,malloc在函數中,而free在函數外。最好在同一調用層上使用這兩個函數。

3) malloc配置設定的記憶體一定要初始化。free後的指針一定要設定為NULL。   

注:雖然現在的作業系統(如:UNIX和Win2k/NT)都有程序記憶體跟蹤機制,也就是如果你有沒有釋放的記憶體,作業系統會幫你釋放。但作業系統依然不會釋放你程式中所有産生了Memory Leak的記憶體,是以,最好還是你自己來做這個工作。(有的時候不知不覺就出現Memory Leak了,而且在幾百萬行的代碼中找無異于海底撈針,Rational有一個工具叫Purify,可能很好的幫你檢查程式中的Memory Leak)

9、變量的初始化

————————

接上一條,變量一定要被初始化再使用。C/C++編譯器在這個方面不會像JAVA一樣幫你初始化,這一切都需要你自己來,如果你使用了沒有初始化的變量,結果未知。好的程式員從來都會在使用變量前初始化變量的。如:

    1) 對malloc配置設定的記憶體進行memset清零操作。(可以使用calloc配置設定一塊全零的記憶體)

    2) 對一些棧上配置設定的struct或數組進行初始化。(最好也是清零)

不過話又說回來了,初始化也會造成系統運作時間有一定的開銷,是以,也不要對所有的變量做初始化,這個也沒有意義。好的程式員知道哪些變量需要初始化,哪些則不需要。如:以下這種情況,則不需要。

        char *pstr;  /* 一個字元串 */

        pstr = ( char* ) malloc( 50 );

        if ( pstr == NULL ) exit(0);

        strcpy( pstr, "Hello Wrold" );

但如果是下面一種情況,最好進行記憶體初始化。(指針是一個危險的東西,一定要初始化)

        char **pstr;  /* 一個字元串數組 */

        pstr = ( char** ) malloc( 50*sizeof(char*) );

        /* 讓數組中的指針都指向NULL */

        memset( pstr, 0, 50*sizeof(char*) ); 

而對于全局變量,和靜态變量,一定要聲明時就初始化。因為你不知道它第一次會在哪裡被使用。是以使用前初始這些變量是比較不現實的,一定要在聲明時就初始化它們。如:

    Links *plnk = NULL;  /* 對于全局變量plnk初始化為NULL */

10、h和c檔案的使用

H檔案和C檔案怎麼用呢?一般來說,H檔案中是declare(聲明),C檔案中是define(定義)。因為C檔案要編譯成庫檔案(Windows下是.obj/.lib,UNIX下是.o/.a),如果别人要使用你的函數,那麼就要引用你的H檔案,是以,H檔案中一般是變量、宏定義、枚舉、結構和函數接口的聲明,就像一個接口說明檔案一樣。而C檔案則是實作細節。

H檔案和C檔案最大的用處就是聲明和實作分開。這個特性應該是公認的了,但我仍然看到有些人喜歡把函數寫在H檔案中,這種習慣很不好。(如果是C++話,對于其模闆函數,在VC中隻有把實作和聲明都寫在一個檔案中,因為VC不支援export關鍵字)。而且,如果在H檔案中寫上函數的實作,你還得在makefile中把頭檔案的依賴關系也加上去,這個就會讓你的makefile很不規範。

最後,有一個最需要注意的地方就是:帶初始化的全局變量不要放在H檔案中!

例如有一個處理錯誤資訊的結構:

    char* errmsg[] = {

        /* 0 */       "No error",                

        /* 1 */       "Open file error",        

        /* 2 */       "Failed in sending/receiving a message",  

        /* 3 */       "Bad arguments",  

        /* 4 */       "Memeroy is not enough",

        /* 5 */       "Service is down; try later",

        /* 6 */       "Unknow information", 

        /* 7 */       "A socket operation has failed", 

        /* 8 */       "Permission denied", 

        /* 9 */       "Bad configuration file format",  

        /* 10 */      "Communication time out", 

        ......

    };

請不要把這個東西放在頭檔案中,因為如果你的這個頭檔案被5個函數庫(.lib或是.a)所用到,于是他就被連結在這5個.lib或.a中,而如果你的一個程式用到了這5個函數庫中的函數,并且這些函數都用到了這個出錯資訊數組。那麼這份資訊将有5個副本存在于你的執行檔案中。如果你的這個errmsg很大的話,而且你用到的函數庫更多的話,你的執行檔案也會變得很大。

正确的寫法應該把它寫到C檔案中,然後在各個需要用到errmsg的C檔案頭上加上 extern char* errmsg[]; 的外部聲明,讓編譯器在連結時才去管他,這樣一來,就隻會有一個errmsg存在于執行檔案中,而且,這樣做很利于封裝。

我曾遇到過的最瘋狂的事,就是在我的目标檔案中,這個errmsg一共有112個副本,執行檔案有8M左右。當我把errmsg放到C檔案中,并為一千多個C檔案加上了extern的聲明後,所有的函數庫檔案尺寸都下降了20%左右,而我的執行檔案隻有5M了。一下子少了3M啊。

[ 備注 ]

—————

有朋友對我說,這個隻是一個特例,因為,如果errmsg在執行檔案中存在多個副本時,可以加快程式運作速度,理由是errmsg的多個複本會讓系統的記憶體換頁降低,達到效率提升。像我們這裡所說的errmsg隻有一份,當某函數要用errmsg時,如果記憶體隔得比較遠,會産生換頁,反而效率不高。

這個說法不無道理,但是一般而言,對于一個比較大的系統,errmsg是比較大的,是以産生副本導緻執行檔案尺寸變大,不僅增加了系統裝載時間,也會讓一個程式在記憶體中占更多的頁面。而對于errmsg這樣資料,一般來說,在系統運作時不會經常用到,是以還是産生的記憶體換頁也就不算頻繁。權衡之下,還是隻有一份errmsg的效率高。即便是像logmsg這樣頻繁使用的的資料,作業系統的記憶體排程算法會讓這樣的頻繁使用的頁面常駐于記憶體,是以也就不會出現記憶體換頁問題了。

本文轉自 haoel 51CTO部落格,原文連結:http://blog.51cto.com/haoel/124711,如需轉載請自行聯系原作者