淺談C代碼的健壯性


        關於代碼的健壯性,我只是略知皮毛,沒有深刻的見解,這里不去講解那些專業的東西,只是記錄我編程的過程中遇到的一些問題及其體會而已。我在此寫下這篇博文,原因除了很久沒有更新博文之外,也只是希望能給那些剛剛接觸編程語言,也許會和我一樣忽略了重點的同學們一點小小的啟發。

        首先我得聲明一點就是,我沒有看過專門講解代碼健壯性之類的書籍(接下來的時間里我會抽時間仔細地去看),所以也許我所說的可能不是很對,當然也可能根本不屬於健壯性這一范疇內,我只把它當作一個小小的開始。如果以后我在這點上有所啟發,我會再次更新這篇博文的。


1.類型安全

        關於類型安全,相信編寫過代碼的同學都深有體會,C語言提供了豐富的數據類型及自由的類型轉換機制。如果編程的過程中,稍有不慎,用錯類型,或者是轉換錯誤,就可能導致程序崩掉或是內存泄漏一系列的問題。這里不想用大篇幅的話語去詳細討論這一話題,只想通過一個小小的例子來說明這一點。所用到的問題代碼如下

#define loop_t unsigned int
#define len_t  unsigned int

#define bool_t int
#define _true  1
#define _false 0

#define record_t int

typedef struct
{
        record_t *record;
        len_t length;
}seqlist_t;

bool_t sort_seqlist(seqlist_t *seq)
{
//	_ENTER_FUNCTION_

	loop_t i, j;
	record_t temp;

	assert((seq != NULL) && (seq->record != NULL));
	
	if (seq->length <= 1) {
		return _true;
	}

	for (i = 1; i < seq->length; i++) {
		j = i - 1;
		temp = seq->record[i];

		while ((j >= 0) && (temp.key < seq->record[j].key)) {
			seq->record[j+1] = seq->record[j];
			j--;			
		}

		seq->record[j+1] = temp;
	}

	return _true;

//	_LEAVE_FUNCTION_
}

        不知道大家是否看出上面這段代碼的問題所在,其實問題就出在數據類型操作失誤。

在排序代碼中,我用到了語句while((j >= 0).....) j--; 其中j的類型為loop_t, 而loop_t的原型為unsigned int,大家都知道,無符號整型是不可能有負值的,所以當j==0時再對它減1,就變成了unsignde int的最大值,而不是想象中的-1,從而導致了接下來對數組越界操作的問題。解決辦法之一就是把上面的unsigned int 換成 int。

        上面出現的問題直接導致了程序的崩潰,而有的時候,雖然問題依舊存在,卻不會導致明顯的錯誤。所以,在編寫代碼的過程中,我們一定要注意類型是否匹配以及操作是否正確。


2.內存泄漏的檢測

        對於一些小程序、運行時間較短的程序來說,內存泄漏之后貌似看不出什么問題,但是對於一些較大的程序,或者需要長期穩定運行的服務程序,一次次的內存泄漏,將會吃掉大部分可用的內存空間,從而導致別的程序無法開啟,或者更嚴重的問題。

        所以養成良好的編程習慣是非常重要的,對於動態開辟的空間,用完一定要記得釋放掉。想是這么想的,如果編寫的代碼很長的話,動態開辟足夠頻繁的話,忽略掉那么一兩處也是很正常不過的。這里介紹兩款很好用的小工具來幫助我們檢測我們所編寫的程序是否存在內存泄漏的問題(關於工具網上教程多的是,這里不多做介紹)。

1.valgrind
        這款工具還是比較好用的,我們只需要正常編寫我們的程序,然后用gcc-g *.c編譯,然后把執行命令傳給它就行,比如valgrind./a.out。它會指出程序中動態開辟空間的數量,以及沒有釋放掉的空間數量。

2.mtrace

        應用這款小工具的時候,需要在代碼中包含<mcheck.h>,還要設置環境變量:setenv(“MALLOC_TRACE”,“output_file_name”, 1);然后調用mtrace();函數。編譯執行我們程序之后,執行mtraceoutput_file_name即可看到內存泄漏的情況


3.memwatch
        memwatch是一組c語言代碼,包含兩個文件:memwatch.c和memwatch.h。只需把這兩個文件拷貝到我們的工程目錄下,在我們編寫的每個代碼文件中加入#include“memwatch.h”,在編譯的時候定義宏MEMWATCH和MW_STDIO,並且和memwatch.c一起編譯就可以了。當我們執行編譯后的程序時,就會在所在目錄下生成memwatch.log日志文件,里面就是程序的內存使用情況的描述。

我自己還是比較喜歡memwatch的,當需要檢測的時候,按照以上方法編譯,不需要的時候正常編譯即可;調試模式下它會幫助我們釋放掉沒有釋放的空間,測試如下代碼(a.c):
#include<stdio.h>
#include<stdlib.h>
#include"memwatch.h"

intmain(int argc, char* argv[])
{
        char*p2;

        p2= (char*) malloc(sizeof(char) * 128);

        return0;
}
        執行編譯命令:gcc-DMEMWATCH -DMEMWATCH_STDIO a.c memwatch.c
        執行測試程序:./a.out

        查看日志文件:vim memwatch.log

        輸出結果如下:

=============MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =============

Startedat Tue Nov 26 10:23:48 2013

Modes:__STDC__ 64-bit mwDWORD==(unsigned int)

mwROUNDALLOC==8sizeof(mwData)==56 mwDataSize==56

Stoppedat Tue Nov 26 10:23:48 2013

unfreed:<1> a.c(9), 128 bytes at 0x1fa8310 {FE FE FE FE FE FE FE FEFE FE FE FE FE FE FE FE ................}
Memoryusage statistics (global):
N)umberof allocations made: 1
L)argestmemory usage : 128
T)otalof all alloc() calls: 128
U)nfreedbytes totals : 128
MEMWATCHdetected 1 anomalies
        從上面的輸出可以看出,在a.c中第9行動態申請的128bytes沒有釋放掉,

        如果正常編譯的話gcca.c
        調用valgrind./a.out 會顯示
totalheap usage: 1 allocs, 0 frees, 128 bytes allocated

…
        如果用gcc-DMEMWATCH a.c memwatch.c編譯的話,再調用valgrind./a.out就會顯示:
All heap blocks were freed – no leaks are possible

        證明memwatch幫助我們把沒有釋放掉的空間釋放掉了。

        雖然有了工具幫助我們,但是良好的編程習慣是長期養成的,平時編寫代碼的時候就應該多加注意,一條malloc就該對應一條free,而不是等待問題出現以后再去檢測。


3.異常處理

        當然,要寫出健壯的C代碼,完善的異常處理機制也是必要的。C語言中,有很多異常處理的方法,常用的有assert、errorno、exit等。通常以上方法不能滿足需求,我們需要自定義異常處理方法。下面自定義一種異常處理機制,以方便我們的編程。

        首先,我自建assertex.h和assert.c,用來保存自定義異常處理接口,內容分別如下:

/*******************************************************
*   File Name: assertex.h                            
* Discription: Custom exception interfaces
* Create Date: 11-26-2013 13:09:39                         
*     Version: 1.0    
*
*      Author: Jensyn 
*       Email: zhangyongjun369@gmail.com                                           
*        BLOG: http://blog.csdn.net/zhangyongjun_2012                     
*******************************************************/
#ifndef _ASSERTEX_H_
#define _ASSERTEX_H_

void __assertex__(const char *file, unsigned int line, const char *function, const char *expr);

#if defined (NDEBUG)
	#define assertex(expr) ((void)0)
//#elif __LINUX__
//	#define assertex(expr) {if(!(expr)){__assertex__(__FILE__,__LINE__,__PRETTY_FUNCTION__,#expr);}}
//#elif __WINDOWS__
//	#define assertex(expr) ((void)0)
#else
	#define assertex(expr) {if(!(expr)){__assertex__(__FILE__,__LINE__,__PRETTY_FUNCTION__,#expr);}}

#endif

#endif


/*******************************************************
*   File Name: assertex.c                            
* Discription:
* Create Date: 11-26-2013 13:23:32                         
*     Version: 1.0            
*
*      Author: Jensyn          
*       EMAIL: zhangyongjun369@gmail.com 
*        BLOG: http://blog.csdn.net/zhangyongjun_2012                    
*******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "assertex.h"

void __assertex__(const char *file, unsigned int line, const char *function, const char *expr)
{
	char assert_info[1024] = {0};
	
	sprintf(assert_info, "At file [%s] line [%d], In function [%s]: Assertion '%s' failed\n", file, line, function, expr);
	printf("%s", assert_info);
	
//	exit(0);
}
        這樣,我們調試的時候,只需要在程序的源文件中包含上面的頭文件,然后把上面的源文件和我們的主程序一塊編譯就可以實現自定義的異常處理了。當然也可以把以上源文件編譯成鏈接庫,再與主程序一塊連接;如果不需要進行錯誤處理,只需要在編譯時定義宏NDEBUG即可

        上面實現的代碼也許在實際中起不到什么作用,實際中需要根據自己的需求去增刪一些功能。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com