初學linux平台上的C編程時間不長,這次正好有一個商務專案需要用到隊列,研究和對比了一下市面上的相關產品,總體而言不是太複雜就是效能達不到要求,所以最後還是決定自己寫一個。這次用C完完全全由自己實現只是第二次,以前都是下個開源軟體改一下,一般來說linux下的軟體只要是C開發的,效能都可以接受。但是為了……,還是自己決定寫一下。在整個開發過程中,碰到的血淚教訓太多了,這裡先記錄一下,第一:警示自己,以後不要再犯了;第二:給有用的人分享一下,別人跳過的坑盡量避免自己再跳(好像我經常會跳??嘻嘻)!
1.記憶體流失;這是一個老問題了,這次開發的mq使用到了tc伺服器介面,本著小心的態度,我幾乎每個malloc的對象都會儘快釋放,並且寫到malloc的時候就緊張,一定要在這個函數中查看一下,是否把malloc的對象都free了。但是tc還是把我害死了,因為用到了從tc中取值的get函數,結果這個函數返回的值需要有開發人員自己free掉的,結果……,後來導致記憶體錯亂才發現了錯誤。引起這個問題的原因是沒有仔細看tc的文檔,咳,杯具。
2.指標參數:這個問題也可以歸結為記憶體流失。對於malloc的變數,free之後一定要置為NULL,這樣我們就可以通過判斷這個自增是否為NULL來編程。所以為了簡單和不至於忘記最後的一個置NULL操作,我把這個過程寫成了函數:
void mem_free(void *ptr)
{
if(NULL != ptr)
{
free(ptr);
ptr = NULL;
}
}
不仔細看沒發現問題吧,把ptr的指標free掉,然後NULL操作,但是問題來了,當我為char *buff 執行mem_free(buff)函數後,發現第二次運行mem_free(buff)發現NULL != ptr竟然為true,鬱悶了吧?這個問題搞了我半天時間,後來查看相關書籍才發現,當第一次mem_free的時候,free確實把記憶體給清除了,但是壞就壞在ptr = NULL;上,注意這個時候ptr只是一個指向buff指標的副本,也就是這個時候運行時態的指標可以理解成這樣ptr->buff->heap,free是因為沒有改變ptr的指向,只是free掉了值,所以heap中的值被清除了,但是ptr = NULL,其實是切斷了ptr –> buff的這根鏈,那麼,buff ->heap這個鏈沒有斷開,所以其實buff還是指向這heap這個記憶體,雖然heap中已經不存在任何有用的資料了。但是我們的本意是要斷開buff –> heap這個鏈,所以這個函數應該寫成傳遞二級指標:
void mem_free(void **ptr)
{
if(NULL != *ptr)
{
free(*ptr);
*ptr = NULL;
}
}
這個問題可以總結為:改變指標指向的內容不需要傳遞指標地址,改變指標的指向,一定要傳遞指標的地址。
3.字串(或者是字元數組,一下稱字串)結束符。對於字串結束符很多人都沒有搞明白,包括之前的我。每個字串都會在最後加上一個’\0’結束符,以示這個字串結束了,如果人為手工沒補齊,那麼自動補齊。我的經驗就是不管什麼時候(聲明char *buff = “i am chinese.”這種時候除外),特別是執行了strcpy或者memcpy後,如果你copy的是字串,那麼一定要手工上去補齊一下,當然,如果你memcpy的是二進位記憶體,那句不需要補齊了。
4.記憶體補齊。這個問題困惑了我2天。比如有一個結構體:
typedef struct header_type
{
char protocol;
int state;
int64_t bodylen;
} header_t;
那麼 sizeof(header_t)和sizeof(char) + sizeof(int)+sizeof(int64_t)這兩者的值一樣嗎?答案是不一樣的。不要說自己看錯了,我確定是不一樣的,至少在我機器上是不一樣的。前者在我機器上的值為16,後者加起來為13.原因就是記憶體補齊。對於c而言,在申請的記憶體的時候會啟用記憶體補齊,補齊規則有的定死了,有的可以調整,具體需要看cpu和編譯器。在我的機器上是2^n規則,1,2,4,8,16…….所以長度為13的補齊後就是16了。那麼如果在同一機器上,這點可以忽略,但是如果你要通過網路傳遞這個結構體,那你就不能忽略這個了。原因有二:如果你直接傳遞這個結構體的變數,那麼需要考慮網路位元組序和記憶體大小端,這個太麻煩,放棄,第二:不考慮原因一的變通方法是全部使用char *傳遞。那麼如果你使用sizeof(header_t)申請記憶體,這個時候用戶端接收到buff傳遞的值是16位的,最後的3位其實是無用的,但是用戶端並不知道,所以會引起記憶體混亂。解決辦法就是申請char *buff記憶體的時候,長度要是真正的記憶體大小,不要把記憶體對齊的空隙加入進去。但是你使用malloc給headet_t的對象申請記憶體時要使用sizeof(header_t),因為不使用sizeof的話就會出現記憶體溢出。
5.recv的時候不管是不是non-block模式,都需要判斷是否逾時。我就碰到這個問題,設定了timeout後,沒有判斷逾時,用戶端接收到伺服器返回的結果非常的不穩定,一會兒好的(在逾時時間內返回),一會兒又都是亂碼。後來發現原來recv返回-1了,並且errno置為11了。解決了這個問題後就不會出現接收不到資料的情況了。
6.位元組長度:因為問題5的出現,導致了我懷疑伺服器端的資料轉送問題,所以就增加了列印header_t的buff的想法,但是這個buff是一個二進位,直接列印二進位不行,只能轉換到16進位,然後列印。所以加入了以下代碼;
char header_buff[14];
bin2hex(buff,13,header_buff);
log(header_buff);
結果程式每次運行到這裡的時候就會引發系統abort的訊號。開始找不到原來。後來經人指點,發現header_buff申請的空間錯了。header_t的二進位長度為13,但是每個位元組16進位表示需要2個位元組,所以13個二進位位元組需要26個位元組(有點繞口),再加上一個字串的結束符,所以應該申請的是27個字元。