記憶體拷貝函式的編寫整理(很流行的面試題)

2022-03-29 03:04:36 字數 2694 閱讀 4164

很多公司都喜歡出關於記憶體拷貝的這麼一道題,下面是本人整理的關於記憶體拷貝函式編寫的過程,僅供參考。

以下是我在一次模擬筆試中寫的程式:

void mymemcpy(char *dst,char *src,int count)

}很顯然,這樣的程式是不合格的。首先,定死了源位址和目標位址的型別,都用了char *型,使得人們使用時,都要通過型別強制轉換來解決,很麻煩。改成下面這樣的程式:

void mymemcpy(void *dst,void *src,int count)

}這樣,把強制轉換的步驟移到了庫的**中,使得使用者方便了,而且一勞永逸。

另外,還有幾個細節要注意,為了實現鏈式表示式,將返回值改為void *。此外,如果將*(char *)dst = *(char *)src;寫反了,編譯也是照樣通過的,而找這個錯誤又要花費很多時間。如果你注意到src所指向的內容在函式內是不應該被改變的,所有對src所指內容的改變都必須被禁止,所以這個引數要用const修飾。改得**如下:

void * mymemcpy(void *dst,const void *src,int count)

return ret;

}此時,有經驗的程式設計師會發現,如果dst傳入了空指標,那麼,程式馬上會掛掉。如果出現這樣的錯誤,程式馬上掛掉,很可能你回找不出錯誤在**,而花費大量時間在**裡尋找bug。解決這類問題的方法如下**所示:

void * mymemcpy(void *dst,const void *src,int count)

while (count--)

return ret;

}上面之所以寫成「if (null==dst||null ==src)」而不是寫成「if (dst == null || src == null)」,也是為了降低犯錯誤的概率。我們知道,在c語言裡面「==」和「=」都是合法的運算子,如果我們不小心寫成了「if (dst = null || src = null)」還是可以編譯通過,而意思卻完全不一樣了,但是如果寫成「if (null=dst||null

=src)」,則編譯的時候就通不過了,所以我們要養成良好的程式設計習慣:常量與變數作條件判斷時應該把常量寫在前面。

上面的**對引數的合法性首先進行了檢查,這樣使得程式掛掉的機率降低了,但是效能就打折扣了,因為每次呼叫都會進行一次判斷,特別是頻繁呼叫和效能比較高的場合,它的效能上的損失就不可忽略了。

如果長期嚴格測試,能夠保證使用者不會使用零位址作為引數呼叫此函式,則希望有簡單的方法關掉引數合法性檢查。我們知道巨集就有某種開關的作用,所繫新版程式如下:

void * mymemcpy(void *dst,const void *src,int count)

#endif

while (count--)

return ret;

}如果在除錯時,我們加入「#define debug"語句,增強程式的健壯性,那麼在除錯過後,我們再改為」#undef debug「語句,提高程式的效能。而事實上,標準庫中已經存在了類似功能的巨集:assert,而且更加好用,它可以在定義debug時,指出**在哪一行檢查失敗,而在沒有定義debug時,完全可以把它當做不存在。assert(_expression_r_r)的使用非常簡單,當_expression_r_r為0時,偵錯程式就出現乙個除錯錯誤,有了這個,**就容易多了。

void * mymemcpy(void *dst,const void *src,int count)

return ret;

}到現在,在語言層面上,出現基本沒有問題了。那麼是否還有問題呢?就要求程式設計師在邏輯上考慮了,這是優秀程式設計師必備的素質,是思維的嚴謹性,否則程式會有非常隱藏的bug。舉例這個例子來說,用下列**來除錯上述程式。

void test()//重疊的記憶體測試

如果你身邊有電腦,你可以試一下,你會發現輸出並不是我們期待的「hhello,world!」(在「hello world!」前加個h),而是「hhhhhhhhhhhhhh」,這是什麼原因呢?原因出在源位址區間和目的位址區間有重疊的地方,v0.6版的程式無意之中將源位址區間的內容修改了!有些反映快的同學馬上會說我從高位址開始拷貝。粗略地看,似乎能解決這個問題,雖然區間是重疊了,但是在修改以前已經拷貝了,所以不影響結果。但是仔細一想,這其實是犯了和上面一樣的思維不嚴謹的錯誤,因為使用者這樣呼叫還是會出錯:

mymemcpy( p, p+1, strlen(p)+1);

所以最完美的解決方案還是判斷源位址和目的位址的大小,才決定到底是從高位址開始拷貝還是低位址開始拷貝。最終**如下:

void * mymemcpy(void *dst,const void *src,int count)

}else

}return(ret);

}我們的程式終於完成了,大家回頭看看第乙個程式,發現第乙個真的是弱爆了。所以,編寫程式一定要嚴謹。

下面附上上面程式的測試程式:

void test()

;mymemcpy(p2,p1,strlen(p1)+1);

printf("%s\n",p2);

mymemcpy(null,p1,strlen(p1)+1);

mymemcpy(p2,null,strlen(p1)+1);

mymemcpy(p1+1,p1,strlen(p1)+1);

printf("%s\n",p1);

mymemcpy(p1,p1+1,strlen(p1)+1);

printf("%s\n",p1);

}本文整理於周立功先生的《卓越的教練是如何訓練高手的?》。

記憶體拷貝函式的實現

記憶體拷貝函式的實現 1 實現memcpy,void memcpy void dst,const void src,size t len 需要考慮記憶體重疊的情況。注意 實際c庫中的memcpy是不考慮記憶體重疊這個問題的,也就是說使用memcpy時,dst和src的位址空間最好不要重疊。如果要考慮...

js 深淺拷貝的整理

好記性不如爛筆頭。記在腦子裡不如做點筆記。什麼是深淺拷貝?在理解這個問題之前,我們要先去了解一下js的資料型別,js資料型別分為基本資料型別和引用資料型別。基本資料型別 string,number,null,undefined,boolean 引用資料型別 object物件 array,functi...

使用拷貝建構函式的理由和作用 整理

因為在系統的學習c 之前,自己寫了一些小玩意,雖然沒有寫拷貝建構函式,但是也沒有報錯。等到真正學習到的時候,就非常疑惑,為什麼要使用拷貝建構函式呢?不寫不是也可以嗎?b 然後,不久之後我就跪了 後來才知道是因為類裡含有指標變數 因為如果不寫拷貝建構函式,系統就只會呼叫預設建構函式,然而預設建構函式是...