|
這里我和大家一起探討c語(yǔ)言的內存使用。
曾經(jīng)有同行寫(xiě)了一個(gè)把整數轉換為字符串的函數:
char *itoa (int n)
{
char retbuf[20];
sprintf(retbuf, "%d", n);
return retbuf;
}
倘若我調用該函數:char *str5 = itoa(5),str5會(huì )是什么數值?
結果是不確定,唯一能確定的是結果不會(huì )是我們想要的 “5”。
為何呢?因為retbuf定義在函數體中,它是局部變量,局部變量的內存空間位于堆棧(stack)中,同時(shí)其作用范圍也僅限于所在的函數中。此時(shí)當itoa()函數返回時(shí),retbuf在堆棧中的內容將被回收,這塊內存地址將可能被存放別的內容。所以把局部變量返回給函數調用者是欠妥的,也是不應該的做法。
這樣我們該如何解決問(wèn)題呢,別擔心,方法有很多且不止一個(gè),下面就來(lái)闡述三種能解決這個(gè)問(wèn)題的方法:
1)、在itoa()函數內部用malloc() 為指針?lè )峙鋬却,同時(shí)將結果存放到里面,最后將retbuf返回給調用者。因為此時(shí)retbuf分配于堆(heap)中,其對應空間不會(huì )隨著(zhù)函數返回而釋放,所以能達到我們的目的。
不過(guò)這里需要注意:調用者在不需要retbuf的時(shí)候必須人工把它釋放,調用free函數來(lái)回收空間,否則就造成內存泄漏了。倘若該函數和調用函數的都是同一個(gè)人所寫(xiě)則問(wèn)題不大,否則將比較容易會(huì )疏漏此釋放內存的動(dòng)作。
2)、在itoa()函數內部定義靜態(tài)變量static char retbuf[20],這同樣能保證函數返回后retbuf的空間不被回收,這是因為靜態(tài)變量并不是存放在堆棧中,而是存放在一個(gè)叫“.bss”段的地方,該地方的內容是不會(huì )因函數返回而被回收的。
這種辦法雖然能解決問(wèn)題,不過(guò)它也導致了函數變成了一個(gè)不可重入函數(即不能保證相同的輸入肯定有相同的輸出)。同時(shí), retbuf [] 中的內容會(huì )被函數的下一次調用結果所代替,該辦法不值得推薦。
3)、使用指針參數,將函數定義為char *itoa(int n, char *retbuf),并且retbuf由函數調用者申請和釋放,這時(shí)候itoa()只是將轉換結果存放到retbuf。
很明顯這種方法比第一、二種方法強,首先避免了方法1對函數的影響,同時(shí)也規避了方法2對內存分配釋放的影響,是業(yè)內一種比較通用流行的做法。
擴展分析:
如果就該問(wèn)題本身而言,想必大家都可以迅速想到答案,問(wèn)題關(guān)鍵就在對memory這類(lèi)敏感資源的正確和合理地利用,下來(lái)我們對內存做個(gè)簡(jiǎn)單的分析:
1)、程序中分為不同的內存段,包含:
.heap - 堆,由程序顯式分配和收回,如果不收回就是內存泄漏。
.bss - 未初始化全局/靜態(tài)變量,在整個(gè)軟件執行過(guò)程中有效;
.data - 已初始化全局/靜態(tài)變量,在整個(gè)軟件執行過(guò)程中有效;
.stack - 函數調用棧,其中的內容在函數執行期間有效,并由編譯器負責分配和收回;
2)、自己管理的內存盡量自己申請和釋放。
這其實(shí)是一個(gè)內存分配和釋放的基本原則,比方說(shuō)上面的第二種方法,由itoa()分配的內存,卻由調用者釋放,就不是一個(gè)十分好的做法,它明顯不如第三種,由調用者自己申請和釋放。此外該原則還有另一層意思:若使用一個(gè)指針,最好先確保它已經(jīng)指向一個(gè)合法地址,否則就自己分配,不然即非法地址訪(fǎng)問(wèn)。許多程序的致命錯誤都是訪(fǎng)問(wèn)一個(gè)沒(méi)有指向合法內存區的指針,也就是野指針,也包括空指針。
問(wèn)題:內存分配 & sizeof
如果使用sizeof來(lái)計算一個(gè)指針變量,希望得到這個(gè)指針變量所分配的內存塊的大小,可以嗎?
char *p = NULL;
int nMemSize = 0;
…
p = malloc(1024);
nMemSize = sizeof(p);
答案與分析:
結果是達不到你的要求的,sizeof只能告訴你指針變量本身占用的內存大小。指針所指向的內存,如果是malloc分配的,sizeof 是無(wú)法知道的。換言之,malloc分配的內存是無(wú)法向內存管理模塊進(jìn)行事后查詢(xún)的,當然你是可以自己編寫(xiě)代碼來(lái)管理維護。
問(wèn)題:棧內存使用
下面的程序運行會(huì )有什么問(wèn)題?
char *GetString(void)
{
char p[] = "hello world";
return p;// 編譯器將提出警告
}
void Test4(void)
{
char *str = NULL;
str = GetString();// str 的內容是垃圾
cout<< str << endl;
}
答案與分析:
返回棧內存,內存可能被銷(xiāo)毀也可能不被及時(shí)銷(xiāo)毀,但可以肯定的是出了作用域之后已被標記成可被系統使用,因此會(huì )返回亂七八糟不可知內容。當然,返回的指針的內容,應該是不變的,特殊時(shí)候是有用的,比如,可以用來(lái)探測系統內存分配規律等。
問(wèn)題:內存使用相關(guān)編程規范
如果想盡可能地避免內存使用上的問(wèn)題,有什么捷徑嗎?
答案與分析:
除非做一件從沒(méi)有人做過(guò)的事情,不然都是有捷徑可言的,答案那就是站在前人的肩膀上,當今各個(gè)大公司都有自己的編碼規范,這些規范凝聚了許多的經(jīng)驗教訓,有較高的使用價(jià)值,考慮到這些規范在網(wǎng)上流傳很多,這里我就不再列舉了,感興趣的,推薦參考林銳的《高質(zhì)量C/C++編程指南》。
|
|