生活随笔
收集整理的這篇文章主要介紹了
流言终结者——C语言内存管理
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
寫在前頭:
我不能保證此文中,我的觀點和理解全是對的,這也不是一篇教學(xué)貼,只是我偶爾突發(fā)奇想了幾個特殊的場景,然后用實驗得到結(jié)果,對結(jié)果進行分析,遂成此文。所以文中肯定存在錯誤,我也沒想到會上首頁,引來眾人圍觀。
最后,歡迎拍磚,我覺得錯了不要緊,改就是了,最慘的是不知道自己錯在哪。
首先看一下man手冊中的定義,
void *malloc(size_t size);
向系統(tǒng)申請size個Bytes長的連續(xù)內(nèi)存,返回一個void類型的指針,指向這塊兒內(nèi)存的首地址。這塊申請到的內(nèi)存是不潔的(也就是非全0x00,內(nèi)容可以是任意的,隨機的)
如果size的值是0,那么返回的指針要么是指向NULL,要么是指向一個unique的地址,這個地址是可以被free釋放的。(這里的解釋是有問題的,例子(8)會證明)
void free(void *ptr);
釋 放ptr指向的內(nèi)存空間,ptr必須是之前調(diào)用過malloc,calloc,realloc這三個函數(shù)返回的,否則,如果free(ptr)已經(jīng)執(zhí)行過 了,而又執(zhí)行一次,那么會導(dǎo)致意外發(fā)生(undefined behavior occurs.),如果ptr指向的是NULL,則不會做任何操作。
(1)假設(shè)有
那么p獲得的內(nèi)存塊的長度到底是多少?能否往里面寫入數(shù)據(jù)?
答:不妨用這段代碼來測試:
| 01 | int main(int argc, char **argv) |
| 03 | ????char *value = NULL;??? |
| 04 | ????char *ori?? = NULL; |
| 07 | ????printf("value[0] is [%c]\n", *value); |
| 11 | ????????printf("value len [%d]\n", strlen(ori)); |
這段代碼結(jié)果如下所示:Fedora14:
| 01 | [michael@localhost mem-test]$ ./a.out ? ? |
| 03 | yydebug:[./mem-test.c]:[34]:value len [1] |
| 04 | yydebug:[./mem-test.c]:[34]:value len [2] |
| 05 | yydebug:[./mem-test.c]:[34]:value len [3] |
| 07 | yydebug:[./mem-test.c]:[34]:value len [135157] |
| 08 | yydebug:[./mem-test.c]:[34]:value len [135158] |
| 09 | yydebug:[./mem-test.c]:[34]:value len [135159] |
| 10 | Segmentation fault (core dumped) |
| 11 | [michael@localhost mem-test]$ |
我重新編譯、運行了很多次,最后打印結(jié)果都是135159;
這 個結(jié)果證明了malloc(0)返回的指針指向的是一個非NULL的內(nèi)存地址處,并且該處的值是0x00,并且對于該進程,不僅該處是可寫的,而且之后的 135159個Bytes也都是可寫的,也就是屬于這個進程空間。直到最后,可能寫到別的進程的空間里面去了,才被內(nèi)核發(fā)來段錯誤信號,自己結(jié)束了。
其實在寫該處就已經(jīng)是內(nèi)存越界了,往后繼續(xù)寫更加是內(nèi)存越界,只是剛好那么巧,該處往后的內(nèi)存塊依然是該進程的有效heap區(qū)間,所以才沒有被內(nèi)核發(fā)段錯誤,而往往這種情況是最慘的,在實際開發(fā)工作中,假設(shè)哪天真的出個這情況,又不會被警告,但是卻有發(fā)現(xiàn)數(shù)據(jù)被竄改。
(2)假設(shè)有
那么稍后p需要用free(p)來釋放,以避免內(nèi)存泄漏嗎?
答:不妨用這段代碼來測試:
| 01 | int main(int argc, char **argv) |
| 03 | ????char *value = NULL; |
| 06 | ????????value = (char*)malloc(0); |
| 07 | ????????printf("value addr [%p]\n", value); |
結(jié)果如下所示:
| 1 | ...(幸虧我及時Ctrl+C停住,運行超過幾秒,電腦就會卡爆了) |
| 4 | value addr [0x87aa5e8]^C |
| 5 | [michael@localhost mem-test]$ |
從打印看來,雖然是調(diào)用malloc(0);,但是每次指向的地址都不同,并且逐漸增大,偏移是0x10,也就是16個字節(jié)。
可以不用while(1)來測試,用一個有限的不是很大的值來測試,然后用top命令來觀察這個進程的資源使用情況,可以看出a.out消耗的內(nèi)存在不斷增加,所以答案就是,malloc(0)申請的內(nèi)存,也要通過free()來釋放,以避免內(nèi)存泄漏。
(3)假設(shè)有
那么會導(dǎo)致進程退出嗎?
答案:不會,free(NULL)相當(dāng)于啥事兒不干。
(4)假設(shè)有
那么會導(dǎo)致進程退出嗎?
答案:不會,進程永遠循環(huán)在while(1)里面,不會出錯退出。
(5)假設(shè)有
那么進程會出錯退出嗎?
答案:進程會出錯退出,打印堆棧信息,提示
| 1 | [michael@localhost mem-test]$ ./a.out |
| 2 | *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09dad008 *** |
類似的信息。所以已經(jīng)free掉的內(nèi)存,除非又申請回來了,否則不能再次去free它,否則進程會出錯。
(6)為什么經(jīng)常有人說free(p);要和p = NULL;一起用,以避免“野指針”的出現(xiàn)?
答案:其實用上面那個例子(5)就能看出,如果free超過1次就會出錯,但是從例子(3)和例子(4)可以看出,free(NULL);可以執(zhí)行任意次 都不會出錯。所以一般free(p);之后,馬上把p指向NULL;,從而即使別人再去執(zhí)行free(p);也不會出現(xiàn)錯誤。不僅如此,通過讓p指向 NULL,也很好的給別人一個提示,你是否對p進行了成功的操作,讓別人好判斷。不妨看看如下的例子:
| 01 | void foo(char *in)//你做的功能函數(shù) |
| 05 | int main(int argc, char **argv) |
| 08 | ????p = strdup("hello_world"); |
| 09 | ????printf("p = [%s], len = [%d]\n", p, strlen(p)); |
| 10 | ????foo(p); //你同事在調(diào)用你的函數(shù) |
| 12 | ????//感謝@<a href="http://my.oschina.net/louisluo" target="_blank" rel="nofollow">ColoredCotton</a>的貢獻 |
| 13 | ????//由于foo函數(shù)的形參是*p,所以無法在foo函數(shù)內(nèi)修改實參指針的指向,所以這的判斷總是true |
| 14 | ????if (NULL != p) {//他不確定你有木有free(),他還很聰明的做了一個判斷 |
| 16 | ????????p = NULL; //他習(xí)慣很好,free之后指向NULL |
假設(shè)foo函數(shù)是你寫的,你同事在調(diào)用foo功能的時候,又不確定你是否free了傳進去的那塊內(nèi)存,于是他就在調(diào)用完foo之后,加了判斷,然后執(zhí)行 free,結(jié)果他得到的結(jié)果是,雖然你free了指針p,但是這僅僅是告訴內(nèi)核,這塊內(nèi)存我不用了,你可以收回了,值得注意的是,p依然指向這塊內(nèi)存,換 句話說也就是指針變量p存的值依然是剛才釋放掉的那塊內(nèi)存的首地址。所以你同事的判斷得到的結(jié)果是true,然后又會執(zhí)行free(p);結(jié)果當(dāng)然和例子 (5)一樣了,如下所示:
| 1 | [michael@localhost mem-test]$ ./a.out |
| 2 | p = [hello_world], len = [11] |
| 3 | *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09088008 *** |
那么“野指針”還可以這樣定義,指向所有非法地址(NULL除外)的指針都可以叫野指針。
那么程序應(yīng)該改成這樣較妥:
| 01 | void foo(char **in)//調(diào)用方式應(yīng)該是傳入某個指針的地址 |
| 03 | ????printf(" &in = [%p], in\'s address\n", &in); |
| 04 | ????printf("? in = [%p], in\'s value\n",? in); |
| 05 | ????printf(" *in = [%p]\n", *in); |
| 06 | ????printf("**in = [%c]\n", **in); |
| 07 | ????free(*in); //*in是實參的指針變量p的指向的被分配的內(nèi)存 |
| 08 | ????*in = NULL; //使得p指向NULL,也就是修改變量p的值 |
| 10 | int main(int argc, char **argv) |
| 13 | ????p = strdup("hello_world"); |
| 14 | ????printf("?? p = [%s], len = [%d]\n", p, strlen(p)); |
| 15 | ????printf("? &p = [%p], p\'s address\n", &p); |
| 16 | ????printf("?? p = [%p], p\'s value\n",? p); |
| 17 | ????printf("? *p = [%c], value of addr No.[%p]\n", *p, p); |
| 18 | ????foo(&p); //這里應(yīng)該傳入p的地址,即&p |
| 20 | ????//感謝@ColoredCotton的貢獻 |
| 21 | ????//而現(xiàn)在,這里的判斷就會是false了 |
| 22 | ????if (NULL != p) { //這里的判斷就有意義了 |
| 27 | ????????printf("p is NULL\n"); |
運行一遍,看看打印如何:
| 01 | [michael@localhost mem-test]$ ./a.out |
| 02 | ???p = [hello_world], len = [11] |
| 03 | ??&p = [0xbf94014c], p's address |
| 04 | ???p = [0x9964008], p's value |
| 05 | ??*p = [h], value of addr No.[0x9964008] |
| 06 | ?&in = [0xbf940130], in's address |
| 07 | ??in = [0xbf94014c], in's value |
| 11 | [michael@localhost mem-test]$ |
我想,根據(jù)打印信息來看,沒什么需要解釋的了。順便還弄透徹了指針以及函數(shù)傳參。
(7)剛malloc后,馬上就free,然后一直循環(huán),會不會總是申請到同一塊內(nèi)存?
答案:這不是真的。不信?你用這些代碼測試一下就知道了:
| 01 | int main(int argc, char **argv) |
| 06 | ????????ra = rand()%100+1; //生成一個1-100之間的隨機數(shù) |
| 07 | ????????if (NULL != (p = (char*)malloc(ra))) { |
| 08 | ????????????printf("p addr [%p], ra = [%d]\n", p, ra); |
看看打印吧:
| 1 | p addr [0x8bd8008], ra = [59] |
| 2 | p addr [0x8bd8048], ra = [78] |
| 3 | p addr [0x8bd8008], ra = [41] |
| 4 | p addr [0x8bd8038], ra = [46] |
| 5 | p addr [0x8bd8070], ra = [82] |
| 6 | p addr [0x8bd8008], ra = [62] |
| 7 | p addr [0x8bd8008], ra = [91] |
| 8 | p addr [0x8bd8008], ra = [24] |
為什么不會一樣呢?這個可以深究一下Linux系統(tǒng)的內(nèi)存分配方式了,這就涉及到內(nèi)核了。
(8)malloc(0)返回的真的入man手冊所說:要么是NULL,要么是一個unique的pointer?
答案:不妨看下這段代碼:
| 01 | int main(int argc, char **argv) |
| 07 | ????????ra = rand()%100+1; |
| 08 | ????????if (NULL == (p = (char*)malloc(ra))) { |
| 09 | ????????????printf("error occurs\n"); |
| 11 | ????????if (NULL != (p0 = malloc(0))) { |
| 12 | ????????????printf("p0 addr [%p]\n", p0); |
打印如下所示:
| 1 | p0 addr [0x97eb008] #我隨便截取了一段打印 |
所以從打印看來,我用Fedora14測試的時候,返回的既不是NULL,也不是一個唯一的地址,我現(xiàn)在也迷惑了,man手冊中的unique到底應(yīng)該如何理解。很遺憾man手冊說得不太準(zhǔn)確。如果你知道為什么,請告訴我,如果我哪一天弄明白了,我會在這里貼出來的。
(9)如果你也和我一樣,做了這么多的實驗,你是不是發(fā)現(xiàn),malloc得到的地址的值總是大于0x80000000的(32bits機器)?
答案:不好意思,我也不知道為什么,做了好多次,不管如何重新編譯、運行,得到的結(jié)果都是大于0x80000000的,如果你知道為什么,也請告訴我,如果我哪一天弄明白了,我會在這里貼出來的。
轉(zhuǎn)載于:https://www.cnblogs.com/shihao/archive/2013/01/25/2876739.html
總結(jié)
以上是生活随笔為你收集整理的流言终结者——C语言内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。