天天看點

memcpy函數的實作及改進

memcpy函數的實作及改進

memcpy主要是用來拷貝不重疊的記憶體塊。下面一步步的來改進memcpy的實作。

最初的解決方法是使memcpy對NULL指針進行檢查,如果指針為NULL,就給出一條錯誤資訊,并中止memcpy的執行。

#include <assert.h>

typedef unsigned char byte;
           
void *mymemcpy(void *pvTo, void *pvFrom, int size) {
	byte *pbTo = (byte *)pvTo;
	byte *pbFrom = (byte *)pvFrom;
	if(NULL == pvTo || NULL == pvFrom) {
		fprintf(stderr, "Bad");
		abort();
	}
	while(size-- > 0)
		*pbTo++ = *pbFrom++;
	return pvTo;
}
           

該實作代碼存在的問題是其中的測試代碼使整個函數的大小增加了一倍,并且降低了該函數的執行速度。

如果儲存兩個版本怎麼樣?一個整潔快速用于程式的傳遞;另一個臃腫緩慢件(因為包括額外的檢查),用于調試。這樣就得同時維護同一程式的兩個版本,并利用C的預處理程式有條件地包含或不包含相應的檢查部分。

void *mymemcpy(void *pvTo, void *pvFrom, int size) {
	byte *pbTo = (byte *)pvTo;
	byte *pbFrom = (byte *)pvFrom;
#ifdef DEBUG
	if(NULL == pvTo || NULL == pvFrom) {
		fprintf(stderr, "Bad");
		abort();
	}
#endif

	while(size-- > 0)
		*pbTo++ = *pbFrom++;
	return pvTo;
}
           

這種想法是同時維護調試和非調試(即傳遞)兩個版本。在程式的編寫過程中,編譯其調試版本,利用它提供的測試部分在增加程式功能時自動查錯。在程式編寫完之後,編譯其傳遞版本,封裝之後交給經銷商。如果保證調試代碼不在最終産品中出現,即既要維護程式的傳遞版本,又要維護程式的調試版本,可以利用斷言來補救。

void *mymemcpy(void *pvTo, void *pvFrom, int size) {
	byte *pbTo = (byte *)pvTo;
	byte *pbFrom = (byte *)pvFrom;
	assert(pvTo != NULL && pvFrom != NULL);
	while(size-- > 0)
		*pbTo++ = *pbFrom++;
	return pvTo;
}
           

assert是個宏。因為要求程式的調試版本和傳遞版本行為完全相同,是以才不把assert作為函數,而把它作為宏。如果assert作為函數的話,其調用就會引起不期望的記憶體或代碼的兌換。使用assert的程式員是把它看成一個在任何系統狀态下都可以安全使用的無害檢測手段。程式員可以重定義assert宏。程式員可以把assert定義成當發生錯誤時不是終止調試程式的執行,而是在發生錯誤的位置轉入調試程式。assert的某些版本甚至可以允許使用者選擇讓程式繼續運作,就仿佛從來沒有發生過錯誤一樣。除非打算在表達式中使用斷言,否則就應該将assert定義為語句。

下面是一種使用者自己定義宏ASSERT的方法:

#ifdef DEBUG
	void _Assert(char *, unsigned);
	#define ASSERT(f)	\
		if(f)	\
			NULL;	\
		else	\
			_Assert(__FILE__, __LINE__)
#else
	#define ASSERT(f)	NULL
#endif

void _Assert(char *strfile, unsigned uLine) {
	fflush(stdout);
	fprintf(stderr, "\nAssertion failed:%s, line %u\n", strfile, uLine);
	fflush(stderr);
	abort();
}
           

在abort之前,需要調用fflush将所有的緩沖輸出寫到标準輸出裝置stdout上。同樣,如果stdout和stderr都指向同一裝置,fflush stdout仍然要放在fflush stderr之前,以確定隻有在所有的輸出都送到stdout之後,fprintf才顯示相應的錯誤資訊。

因為memcpy是拷貝的不重疊的記憶體塊,是以還需要增加記憶體塊不重疊的斷言。

void *mymemcpy(void *pvTo, void *pvFrom, int size) {
	byte *pbTo = (byte *)pvTo;
	byte *pbFrom = (byte *)pvFrom;
	ASSERT(pvTo != NULL && pvFrom != NULL);
	//記憶體重疊嗎?如果重疊,就使用memmove。
	assert(pbTo >= pbFrom+size || pbFrom >= pbTo+size);

	while(size-- > 0)
		*pbTo++ = *pbFrom++;
	return pvTo;
}