1.如何使用 malloc 函數(shù)
不要莫名其妙,其實(shí)上面這段小小的對(duì)話,就是malloc的使用過(guò)程。malloc是一個(gè)函數(shù),專門用來(lái)從堆上分配內(nèi)存。使用malloc函數(shù)需要幾個(gè)要求:
內(nèi)存分配給誰(shuí)?
分配多大內(nèi)存?
是否還有足夠內(nèi)存分配?
內(nèi)存的將用來(lái)存儲(chǔ)什么格式的數(shù)據(jù),即內(nèi)存用來(lái)做什么?
分配好的內(nèi)存在哪里?
如果這五點(diǎn)都確定,那內(nèi)存就能分配。下面先看malloc函數(shù)的原型:
1 | ( void *)malloc( int size) |
看到了沒有,這里的返回類型是(void *),這是多巧妙的一個(gè)設(shè)計(jì)啊。
malloc函數(shù)的返回值是一個(gè)void類型的指針,參數(shù)為int類型數(shù)據(jù),即申請(qǐng)分配的內(nèi)存大小,單位是byte。內(nèi)存分配成功之后,malloc函數(shù)返回這塊內(nèi)存的首地址。你需要一個(gè)指針來(lái)接收這個(gè)地址。但是由于函數(shù)的返回值是void *類型的,所以必須強(qiáng)制轉(zhuǎn)換成你所接收的類型。也就是說(shuō),這塊內(nèi)存將要用來(lái)存儲(chǔ)什么類型的數(shù)據(jù)。比如:
1 | char *p = ( char *)malloc( 100 ); |
在堆上分配了100個(gè)字節(jié)內(nèi)存,返回這塊內(nèi)存的首地址,把地址強(qiáng)制轉(zhuǎn)換成char *類型后賦給char *類型的指針變量p。同時(shí)告訴我們這塊內(nèi)存將用來(lái)存儲(chǔ)char類型的數(shù)據(jù)。也就是說(shuō)你只能通過(guò)指針變量p來(lái)操作這塊內(nèi)存。這塊內(nèi)存本身并沒有名字,對(duì)它的訪問(wèn)是匿名訪問(wèn)。
上面就是使用malloc函數(shù)成功分配一塊內(nèi)存的過(guò)程。但是,每次你都能分配成功嗎?
不一定。
函數(shù)同樣要注意這點(diǎn):如果所申請(qǐng)的內(nèi)存塊大于目前堆上剩余內(nèi)存塊(整塊),則內(nèi)存分配會(huì)失敗,函數(shù)返回NULL。注意這里說(shuō)的“堆上剩余內(nèi)存塊”不是所有剩余內(nèi)存塊之和,因?yàn)閙alloc函數(shù)申請(qǐng)的是連續(xù)的一塊內(nèi)存。既然malloc函數(shù)申請(qǐng)內(nèi)存有不成功的可能,那我們?cè)谑褂弥赶蜻@塊內(nèi)存的指針時(shí),必須用if(NULL!=p)語(yǔ)句來(lái)驗(yàn)證內(nèi)存確實(shí)分配成功了。
2. 用 malloc 函數(shù)申請(qǐng) 0 字節(jié)內(nèi)存
另外還有一個(gè)問(wèn)題:用malloc函數(shù)申請(qǐng)0字節(jié)內(nèi)存會(huì)返回NULL指針嗎?
可以測(cè)試一下,也可以去查找關(guān)于malloc函數(shù)的說(shuō)明文檔。申請(qǐng)0字節(jié)內(nèi)存,函數(shù)并不返回NULL,而是返回一個(gè)正常的內(nèi)存地址。但是你卻無(wú)法使用這塊大小為0的內(nèi)存。這好尺子上的某個(gè)刻度,刻度本身并沒有長(zhǎng)度,只有某兩個(gè)刻度一起才能量出長(zhǎng)度。對(duì)于這一點(diǎn)一定要小心,因?yàn)檫@時(shí)候if(NULL!=p)語(yǔ)句校驗(yàn)將不起作用。
3.內(nèi)存釋放
既然有分配,那就必須有釋放。不然的話,有限的內(nèi)存總會(huì)用光,而沒有釋放的內(nèi)存卻在空閑。與malloc對(duì)應(yīng)的就是free函數(shù)了。free函數(shù)只有一個(gè)參數(shù),就是所要釋放的內(nèi)存塊的首地址。比如上例:
1 | free(p); |
free函數(shù)看上去挺狠的,但它到底作了什么呢?
其實(shí)它就做了一件事:斬?cái)嘀羔樧兞颗c這塊內(nèi)存的關(guān)系。
比如上面的例子,我們可以說(shuō)malloc函數(shù)分配的內(nèi)存塊是屬于p的,因?yàn)槲覀儗?duì)這塊內(nèi)存的訪問(wèn)都需要通過(guò)p來(lái)進(jìn)行。free函數(shù)就是把這塊內(nèi)存和p之間的所有關(guān)系斬?cái)唷拇藀和那塊內(nèi)存之間再無(wú)瓜葛。至于指針變量p本身保存的地址并沒有改變,但是它對(duì)這個(gè)地址處的那塊內(nèi)存卻已經(jīng)沒有所有權(quán)了。那塊被釋放的內(nèi)存里面保存的值也沒有改變,只是再也沒有辦法使用了。
這就是free函數(shù)的功能。按照上面的分析,如果對(duì)p連續(xù)兩次以上使用free函數(shù),肯定會(huì)發(fā)生錯(cuò)誤。因?yàn)榈谝皇褂胒ree函數(shù)時(shí),p所屬的內(nèi)存已經(jīng)被釋放,第二次使用時(shí)已經(jīng)無(wú)內(nèi)存可釋放了。關(guān)于這點(diǎn),我(陳正沖老師)上課時(shí)讓學(xué)生記住的是:一定要一夫一妻制,不然肯定出錯(cuò)。
malloc兩次只free一次會(huì)內(nèi)存泄漏;malloc一次free兩次肯定會(huì)出錯(cuò)。也就是說(shuō),在程序中malloc的使用次數(shù)一定要和free相等,否則必有錯(cuò)誤。這種錯(cuò)誤主要發(fā)生在循環(huán)使用malloc函數(shù)時(shí),往往把malloc和free次數(shù)弄錯(cuò)了。
4.內(nèi)存釋放之后
既然使用free函數(shù)之后指針變量p本身保存的地址并沒有改變,那我們就需要重新把p的值變?yōu)镹ULL:
1 | p = NULL; |
這個(gè)NULL就是我們前面所說(shuō)的“栓野狗的鏈子”。如果你不栓起來(lái)遲早會(huì)出問(wèn)題的。比如:
在free(p)之后,你用if(NULL!=p)這樣的校驗(yàn)語(yǔ)句還能起作用嗎?
例如:
1 2 3 4 5 6 7 8 9 | char *p = ( char *) malloc( 100 ); strcpy(p, “hello”); free(p); /* p 所指的內(nèi)存被釋放,但是p所指的地址仍然不變*/
if (NULL != p) { /* 沒有起到防錯(cuò)作用*/ strcpy(p, “world”); /* 出錯(cuò)*/ } |
釋放完塊內(nèi)存之后,沒有把指針置NULL,這個(gè)指針就成為了“野指針”,也有書叫“懸垂指針”。這是很危險(xiǎn)的,而且也是經(jīng)常出錯(cuò)的地方。所以一定要記住一條:free完之后,一定要給指針置NULL。
5.內(nèi)存已經(jīng)被釋放了,但是繼續(xù)通過(guò)指針來(lái)使用
這里一般有三種情況:
第一種:就是上面所說(shuō)的,free(p)之后,繼續(xù)通過(guò)p指針來(lái)訪問(wèn)內(nèi)存。解決的辦法就是給p置NULL。
第二種:函數(shù)返回棧內(nèi)存。這是初學(xué)者最容易犯的錯(cuò)誤。比如在函數(shù)內(nèi)部定義了一個(gè)數(shù)組,卻用return語(yǔ)句返回指向該數(shù)組的指針。解決的辦法就是弄明白棧上變量的生命周期。
第三種:內(nèi)存使用太復(fù)雜,弄不清到底哪塊內(nèi)存被釋放,哪塊沒有被釋放。解決的辦法是重新設(shè)計(jì)程序,改善對(duì)象之間的調(diào)用關(guān)系。
參考:陳正沖老師的《c語(yǔ)言深度剖析》。
聯(lián)系客服