C语言深度剖析书籍学习记录 第五章 内存管理
生活随笔
收集整理的這篇文章主要介紹了
C语言深度剖析书籍学习记录 第五章 内存管理
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
?常見的內(nèi)存錯(cuò)誤
-
定義了指針變量,但是沒有為指針分配內(nèi)存,即指針沒有指向一塊合法的內(nèi)存。
結(jié)構(gòu)體成員指針未初始化
- ?很多初學(xué)者犯了這個(gè)錯(cuò)誤還不知道是怎么回事。這里定義了結(jié)構(gòu)體變量 stu,但是他沒 想到這個(gè)結(jié)構(gòu)體內(nèi)部 char *name 這成員在定義結(jié)構(gòu)體變量 stu 時(shí),只是給 name 這個(gè)指針變 量本身分配了 4 個(gè)字節(jié)。name 指針并沒有指向一個(gè)合法的地址,這時(shí)候其內(nèi)部存的只是一 些亂碼。所以在調(diào)用 strcpy 函數(shù)時(shí),會(huì)將字串"Jimy"往亂碼所指的內(nèi)存上拷貝,而這塊內(nèi)存 name 指針根本就無(wú)權(quán)訪問,導(dǎo)致出錯(cuò)。解決的辦法是為 name 指針 malloc 一塊空間。 同樣,也有人犯如下錯(cuò)誤:
- ?為指針變量 pstu 分配了內(nèi)存,但是同樣沒有給 name 指針分配內(nèi)存。錯(cuò)誤與上面第一種 情況一樣,解決的辦法也一樣。這里用了一個(gè) malloc 給人一種錯(cuò)覺,以為也給 name 指針分 配了內(nèi)存。
- 不管什么時(shí)候,我們使用指針之前一定要確保指針是有效的。
- 一般在函數(shù)入口處使用 assert(NULL != p)對(duì)參數(shù)進(jìn)行校驗(yàn)。在非參數(shù)的地方使用 if(NULL != p)來校驗(yàn)。但這都有一個(gè)要求,即 p 在定義的同時(shí)被初始化為 NULL 了。比 如上面的例子,即使用 if(NULL != p)校驗(yàn)也起不了作用,因?yàn)?name 指針并沒有被初始 化為 NULL,其內(nèi)部是一個(gè)非 NULL 的亂碼。?
- 使用指針之前需要對(duì)其賦值為NULL? ?使用完之后也需要將其賦值為 NULL
- assert 是一個(gè)宏,而不是函數(shù),包含在 assert.h 頭文件中。如果其后面括號(hào)里的值為假, 則程序終止運(yùn)行,并提示出錯(cuò);如果后面括號(hào)里的值為真,則繼續(xù)運(yùn)行后面的代碼。這個(gè) 宏只在 Debug 版本上起作用,而在 Release 版本被編譯器完全優(yōu)化掉,這樣就不會(huì)影響代碼 的性能。
- 有人也許會(huì)問,既然在 Release 版本被編譯器完全優(yōu)化掉,那 Release 版本是不是就完 全沒有這個(gè)參數(shù)入口校驗(yàn)了呢?這樣的話那不就跟不使用它效果一樣嗎?
是的,使用 assert 宏的地方在 Release 版本里面確實(shí)沒有了這些校驗(yàn)。但是我們要知道, assert 宏只是幫助我們調(diào)試代碼用的,它的一切作用就是讓我們盡可能的在調(diào)試函數(shù)的時(shí)候 把錯(cuò)誤排除掉,而不是等到 Release 之后。它本身并沒有除錯(cuò)功能。再有一點(diǎn)就是,參數(shù)出現(xiàn)錯(cuò)誤并非本函數(shù)有問題,而是調(diào)用者傳過來的實(shí)參有問題。assert 宏可以幫助我們定位錯(cuò)誤,而不是排除錯(cuò)誤。
為指針分配的內(nèi)存太小
- char *p1 = “abcdefg”;
- char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
- strcpy(p2,p1);
- p1 是字符串常量,其長(zhǎng)度為 7 個(gè)字符,但其所占內(nèi)存大小為 8 個(gè) byte。初學(xué)者往往忘 了字符串常量的結(jié)束標(biāo)志“\0”。這樣的話將導(dǎo)致 p1 字符串中最后一個(gè)空字符“\0”沒有被 拷貝到 p2 中。解決的辦法是加上這個(gè)字符串結(jié)束標(biāo)志符:
- char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));
- 這里需要注意的是,只有字符串常量才有結(jié)束標(biāo)志符。比如下面這種寫法就沒有結(jié)束標(biāo)志符 了:
- char a[7] = {‘a(chǎn)’,’b’,’c’,’d’,’e’,’f’,’g’};
- 另外,不要因?yàn)?char 類型大小為 1 個(gè) byte 就省略 sizof(char)這種寫法。這樣只會(huì)使你的代碼可移植性下降。
內(nèi)存分配成功,但并未初始化
- 犯這個(gè)錯(cuò)誤往往是由于沒有初始化的概念或者是以為內(nèi)存分配好之后其值自然為0。未初始化指針變量也許看起來不那么嚴(yán)重,但是它確確實(shí)實(shí)是個(gè)非常嚴(yán)重的問題,而且往往 出現(xiàn)這種錯(cuò)誤很難找到原因。
- int i = 10;? char *p = (char *)malloc(sizeof(char)); 但是往往這個(gè)時(shí)候我們還不確定這個(gè)變量的初值,這樣的話可以初始化為 0 或 NULL。
- int i=0;? char *p = NULL; 如果定義的是數(shù)組的話,可以這樣初始化: int a[10] = {0};? 或者用 memset 函數(shù)來初始化為 0:? memset(a,0,sizeof(a));
- memset 函數(shù)有三個(gè)參數(shù),第一個(gè)是要被設(shè)置的內(nèi)存起始地址;第二個(gè)參數(shù)是要被設(shè)置 的值;第三個(gè)參數(shù)是要被設(shè)置的內(nèi)存大小,單位為 byte。
- 至于指針變量如果未被初始化,會(huì)導(dǎo)致 if 語(yǔ)句或 assert 宏校驗(yàn)失敗。因?yàn)橹羔樦赶驍?shù)據(jù)存放的是亂碼,但是系統(tǒng)認(rèn)為有數(shù)據(jù)是正確的,所以不對(duì)其存儲(chǔ)的數(shù)據(jù)和默認(rèn)的數(shù)值進(jìn)行比較就無(wú)法判定數(shù)據(jù)的有效性
內(nèi)存越界
- 內(nèi)存分配成功,且已經(jīng)初始化,但是操作越過了內(nèi)存的邊界。 這種錯(cuò)誤經(jīng)常是由于操作數(shù)組或指針時(shí)出現(xiàn)“多 1”或“少 1”,常發(fā)生于循環(huán)遍歷
- 所以,for 循環(huán)的循環(huán)變量一定要使用半開半閉的區(qū)間,而且如果不是特殊情況,循環(huán)變 量盡量從 0 開始
內(nèi)存泄露
- 會(huì)產(chǎn)生泄漏的內(nèi)存就是堆上的內(nèi)存(這里不討論資源或句柄等泄漏情況),也就是說由 malloc 系列函數(shù)或 new 操作符分配的內(nèi)存。如果用完之后沒有及時(shí) free 或 delete,這塊內(nèi)存 就無(wú)法釋放,直到整個(gè)程序終止
- (void *)malloc(int size)
- malloc 函數(shù)的返回值是一個(gè) void 類型的指針,參數(shù)為 int 類型數(shù)據(jù),即申請(qǐng)分配的內(nèi)存 大小,單位是 byte。內(nèi)存分配成功之后,malloc 函數(shù)返回這塊內(nèi)存的首地址。你需要一個(gè)指 針來接收這個(gè)地址。但是由于函數(shù)的返回值是 void *類型的,所以必須強(qiáng)制轉(zhuǎn)換成你所接收 的類型。也就是說,這塊內(nèi)存將要用來存儲(chǔ)什么類型的數(shù)據(jù)。
- 比如:char *p = (char *)malloc(100);在堆上分配了 100 個(gè)字節(jié)內(nèi)存,返回這塊內(nèi)存的首地址,把地址強(qiáng)制轉(zhuǎn)換成 char *類型后賦 給 char *類型的指針變量 p。同時(shí)告訴我們這塊內(nèi)存將用來存儲(chǔ) char 類型的數(shù)據(jù)。也就是說 你只能通過指針變量 p 來操作這塊內(nèi)存。這塊內(nèi)存本身并沒有名字,對(duì)它的訪問是匿名訪 問。
- 上面就是使用 malloc 函數(shù)成功分配一塊內(nèi)存的過程。但是,每次你都能分配成功嗎? 不一定。使用 malloc 函數(shù)同樣要注意這點(diǎn):如果所申請(qǐng)的內(nèi)存塊大于目前堆上剩余內(nèi)存塊(整塊),則內(nèi)存分配 會(huì)失敗,函數(shù)返回 NULL。注意這里說的“堆上剩余內(nèi)存塊”不是所有剩余內(nèi)存塊之和,因?yàn)?malloc 函數(shù)申請(qǐng)的是連續(xù)的一塊內(nèi)存。
既然 malloc 函數(shù)申請(qǐng)內(nèi)存有不成功的可能,那我們?cè)谑褂弥赶蜻@塊內(nèi)存的指針時(shí),必 須用 if(NULL != p)語(yǔ)句來驗(yàn)證內(nèi)存確實(shí)分配成功了。
使用malloc函數(shù)申請(qǐng) 0 字節(jié)內(nèi)存
- 另外還有一個(gè)問題:用 malloc 函數(shù)申請(qǐng) 0 字節(jié)內(nèi)存會(huì)返回 NULL 指針嗎?
- 可以測(cè)試一下,也可以去查找關(guān)于 malloc 函數(shù)的說明文檔。申請(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)將不起作用。
內(nèi)存釋放
- 既然有分配,那就必須有釋放。不然的話,有限的內(nèi)存總會(huì)用光,而沒有釋放的內(nèi)存 卻在空閑。與 malloc 對(duì)應(yīng)的就是 free 函數(shù)了。free 函數(shù)只有一個(gè)參數(shù),就是所要釋放的內(nèi) 存塊的首地址。
- 比如上例:? ?free(p);
- free 函數(shù)看上去挺狠的,但它到底作了什么呢?其實(shí)它就做了一件事:斬?cái)嘀羔樧兞颗c這塊內(nèi)存的關(guān)系。比如上面的例子,我們可以說 malloc 函數(shù)分配的內(nèi)存塊是屬于 p 的,因 為我們對(duì)這塊內(nèi)存的訪問都需要通過 p 來進(jìn)行。free 函數(shù)就是把這塊內(nèi)存和 p 之間的所有關(guān)系斬?cái)唷拇?p 和那塊內(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)榈谝皇褂?free 函數(shù)時(shí),p 所屬的內(nèi)存已經(jīng)被釋放,第二次使用時(shí)已經(jīng)無(wú) 內(nèi)存可釋放了。關(guān)于這點(diǎn),我上課時(shí)讓學(xué)生記住的是:一定要一夫一妻制,不然肯定出錯(cuò)。內(nèi)存釋放之后需要將指針重新設(shè)置為NULL
內(nèi)存已經(jīng)被釋放了,但是繼續(xù)通過指針來使用
- 這里一般有三種情況:
- 第一種:就是上面所說的,free(p)之后,繼續(xù)通過 p 指針來訪問內(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)系。
總結(jié)
以上是生活随笔為你收集整理的C语言深度剖析书籍学习记录 第五章 内存管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STL源码剖析 deque双端队列 概述
- 下一篇: Switch必须玩大作(ns十大最可玩游