0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

如意 ? 來源:C語言與C++編程 ? 作者:雨鈍風(fēng)輕 ? 2020-10-19 11:55 ? 次閱讀

一、指針沒有指向一塊合法的內(nèi)存

定義了指針變量,但是沒有為指針分配內(nèi)存,即指針沒有指向一塊合法的內(nèi)存。淺顯的例子就不舉了,這里舉幾個比較隱蔽的例子。

1、結(jié)構(gòu)體成員指針未初始化

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

很多初學(xué)者犯了這個錯誤還不知道是怎么回事。這里定義了結(jié)構(gòu)體變量stu,但是他沒想到這個結(jié)構(gòu)體內(nèi)部char *name 這成員在定義結(jié)構(gòu)體變量stu 時,只是給name 這個指針變量本身分配了4 個字節(jié)。name 指針并沒有指向一個合法的地址,這時候其內(nèi)部存的只是一些亂碼。所以在調(diào)用strcpy 函數(shù)時,會將字符串“Jimy”往亂碼所指的內(nèi)存上拷貝,而這塊內(nèi)存name 指針根本就無權(quán)訪問,導(dǎo)致出錯。解決的辦法是為name 指針malloc 一塊空間。

同樣,也有人犯如下錯誤:

為指針變量pstu 分配了內(nèi)存,但是同樣沒有給name 指針分配內(nèi)存。錯誤與上面第一種情況一樣,解決的辦法也一樣。這里用了一個malloc 給人一種錯覺,以為也給name 指針分配了內(nèi)存。

2、沒有為結(jié)構(gòu)體指針分配足夠的內(nèi)存

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

為pstu 分配內(nèi)存的時候,分配的內(nèi)存大小不合適。這里把sizeof(struct student)誤寫為sizeof(struct student*)。當(dāng)然name 指針同樣沒有被分配內(nèi)存。解決辦法同上。

3、函數(shù)的入口校驗(yàn)

不管什么時候,我們使用指針之前一定要確保指針是有效的。

一般在函數(shù)入口處使用assert(NULL != p)對參數(shù)進(jìn)行校驗(yàn)。在非參數(shù)的地方使用if(NULL != p)來校驗(yàn)。但這都有一個要求,即p 在定義的同時被初始化為NULL 了。比如上面的例子,即使用if(NULL != p)校驗(yàn)也起不了作用,因?yàn)閚ame 指針并沒有被初始化為NULL,其內(nèi)部是一個非NULL 的亂碼。

assert 是一個宏,而不是函數(shù),包含在assert.h 頭文件中。如果其后面括號里的值為假,則程序終止運(yùn)行,并提示出錯;如果后面括號里的值為真,則繼續(xù)運(yùn)行后面的代碼。這個宏只在Debug 版本上起作用,而在Release 版本被編譯器完全優(yōu)化掉,這樣就不會影響代碼的性能。

有人也許會問,既然在Release 版本被編譯器完全優(yōu)化掉,那Release 版本是不是就完全沒有這個參數(shù)入口校驗(yàn)了呢?這樣的話那不就跟不使用它效果一樣嗎?

是的,使用assert 宏的地方在Release 版本里面確實(shí)沒有了這些校驗(yàn)。但是我們要知道,assert 宏只是幫助我們調(diào)試代碼用的,它的一切作用就是讓我們盡可能的在調(diào)試函數(shù)的時候把錯誤排除掉,而不是等到Release 之后。它本身并沒有除錯功能。再有一點(diǎn)就是,參數(shù)出現(xiàn)錯誤并非本函數(shù)有問題,而是調(diào)用者傳過來的實(shí)參有問題。assert 宏可以幫助我們定位錯誤,而不是排除錯誤。

二、為指針分配的內(nèi)存太小

為指針分配了內(nèi)存,但是內(nèi)存大小不夠,導(dǎo)致出現(xiàn)越界錯誤。

char *p1 = “abcdefg”; char *p2 = (char *)malloc(sizeof(char)*strlen(p1)); strcpy(p2,p1);

p1 是字符串常量,其長度為7 個字符,但其所占內(nèi)存大小為8 個byte。初學(xué)者往往忘了字符串常量的結(jié)束標(biāo)志“\0”。這樣的話將導(dǎo)致p1 字符串中最后一個空字符“\0”沒有被拷貝到p2 中。解決的辦法是加上這個字符串結(jié)束標(biāo)志符:

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

這里需要注意的是,只有字符串常量才有結(jié)束標(biāo)志符。比如下面這種寫法就沒有結(jié)束標(biāo)志符了:

char a[7] = {‘a(chǎn)’,’b’,’c’,’d’,’e’,’f’,’g’};

另外,不要因?yàn)閏har 類型大小為1 個byte 就省略sizof(char)這種寫法。這樣只會使你的代碼可移植性下降。

三、內(nèi)存分配成功,但并未初始化

犯這個錯誤往往是由于沒有初始化的概念或者是以為內(nèi)存分配好之后其值自然為0。未初始化指針變量也許看起來不那么嚴(yán)重,但是它確確實(shí)實(shí)是個非常嚴(yán)重的問題,而且往往出現(xiàn)這種錯誤很難找到原因。

曾經(jīng)有一個學(xué)生在寫一個windows 程序時,想調(diào)用字庫的某個字體。而調(diào)用這個字庫需要填充一個結(jié)構(gòu)體。他很自然的定義了一個結(jié)構(gòu)體變量,然后把他想要的字庫代碼賦值給了相關(guān)的變量。但是,問題就來了,不管怎么調(diào)試,他所需要的這種字體效果總是不出來。我在檢查了他的代碼之后,沒有發(fā)現(xiàn)什么問題,于是單步調(diào)試。在觀察這個結(jié)構(gòu)體變量的內(nèi)存時,發(fā)現(xiàn)有幾個成員的值為亂碼。就是其中某一個亂碼惹得禍!因?yàn)橄到y(tǒng)會按照這個結(jié)構(gòu)體中的某些特定成員的值去字庫中尋找匹配的字體,當(dāng)這些值與字庫中某種字體的某些項匹配時,就調(diào)用這種字體。但是很不幸,正是因?yàn)檫@幾個亂碼,導(dǎo)致沒有找到相匹配的字體!因?yàn)橄到y(tǒng)并無法區(qū)分什么數(shù)據(jù)是亂碼,什么數(shù)據(jù)是有效的數(shù)據(jù)。只要有數(shù)據(jù),系統(tǒng)就理所當(dāng)然的認(rèn)為它是有效的。

也許這種嚴(yán)重的問題并不多見,但是也絕不能掉以輕心。所以在定義一個變量時,第一件事就是初始化。你可以把它初始化為一個有效的值,比如:

int i = 10; char *p = (char *)malloc(sizeof(char));

但是往往這個時候我們還不確定這個變量的初值,這樣的話可以初始化為0 或NULL。

int i = 0; char *p = NULL;

如果定義的是數(shù)組的話,可以這樣初始化:

int a[10] = {0};

或者用memset 函數(shù)來初始化為0:

memset(a,0,sizeof(a));

memset 函數(shù)有三個參數(shù),第一個是要被設(shè)置的內(nèi)存起始地址;第二個參數(shù)是要被設(shè)置的值;第三個參數(shù)是要被設(shè)置的內(nèi)存大小,單位為byte。這里并不想過多的討論memset 函數(shù)的用法,如果想了解更多,請參考相關(guān)資料

至于指針變量如果未被初始化,會導(dǎo)致if 語句或assert 宏校驗(yàn)失敗。這一點(diǎn),上面已有分析。

四、內(nèi)存越界

內(nèi)存分配成功,且已經(jīng)初始化,但是操作越過了內(nèi)存的邊界。這種錯誤經(jīng)常是由于操作數(shù)組或指針時出現(xiàn)“多1”或“少1”。比如:

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

所以,for 循環(huán)的循環(huán)變量一定要使用半開半閉的區(qū)間,而且如果不是特殊情況,循環(huán)變量盡量從0 開始。

五、內(nèi)存泄漏

內(nèi)存泄漏幾乎是很難避免的,不管是老手還是新手,都存在這個問題。甚至包括windows,Linux 這類軟件,都或多或少有內(nèi)存泄漏。也許對于一般的應(yīng)用軟件來說,這個問題似乎不是那么突出,重啟一下也不會造成太大損失。但是如果你開發(fā)的是嵌入式系統(tǒng)軟件呢?比如汽車制動系統(tǒng),心臟起搏器等對安全要求非常高的系統(tǒng)。你總不能讓心臟起搏器重啟吧,人家閻王老爺是非常好客的。

會產(chǎn)生泄漏的內(nèi)存就是堆上的內(nèi)存(這里不討論資源或句柄等泄漏情況),也就是說由malloc 系列函數(shù)或new 操作符分配的內(nèi)存。如果用完之后沒有及時free 或delete,這塊內(nèi)存就無法釋放,直到整個程序終止。

1、告老還鄉(xiāng)求良田

怎么去理解這個內(nèi)存分配和釋放過程呢?先看下面這段對話:

萬歲爺:愛卿,你為朕立下了汗馬功勞,想要何賞賜?。?/p>

某功臣:萬歲,黃金白銀,臣視之如糞土。臣年歲已老,欲告老還鄉(xiāng)。臣乞良田千畝以蔭后世,別無他求。

萬歲爺:愛卿,你勞苦功高,卻僅要如此小賞,朕今天就如你所愿。戶部劉侍郎,查看湖廣一帶是否還有千畝上等良田未曾封賞。

劉侍郎:長沙尚有五萬余畝上等良田未曾封賞。

萬歲爺:在長沙撥良田千畝封賞愛卿。愛卿,良田千畝,你欲何用???

某功臣:謝萬歲。長沙一帶,適合種水稻,臣想用來種水稻。種水稻需要把田分為一畝一塊,方便耕種。

。。。。

2、如何使用malloc 函數(shù)

不要莫名其妙,其實(shí)上面這段小小的對話,就是malloc 的使用過程。malloc 是一個函數(shù),專門用來從堆上分配內(nèi)存。使用malloc 函數(shù)需要幾個要求:

內(nèi)存分配給誰?這里是把良田分配給某功臣。

分配多大內(nèi)存?這里是分配一千畝。

是否還有足夠內(nèi)存分配?這里是還有足夠良田分配。

內(nèi)存的將用來存儲什么格式的數(shù)據(jù),即內(nèi)存用來做什么?

這里是用來種水稻,需要把田分成一畝一塊。分配好的內(nèi)存在哪里?這里是在長沙。

如果這五點(diǎn)都確定,那內(nèi)存就能分配。下面先看malloc 函數(shù)的原型: (void *)malloc(int size) malloc 函數(shù)的返回值是一個void 類型的指針,參數(shù)為int 類型數(shù)據(jù),即申請分配的內(nèi)存大小,單位是byte。內(nèi)存分配成功之后,malloc 函數(shù)返回這塊內(nèi)存的首地址。你需要一個指針來接收這個地址。但是由于函數(shù)的返回值是void *類型的,所以必須強(qiáng)制轉(zhuǎn)換成你所接收的類型。也就是說,這塊內(nèi)存將要用來存儲什么類型的數(shù)據(jù)。比如: char *p = (char *)malloc(100); 在堆上分配了100 個字節(jié)內(nèi)存,返回這塊內(nèi)存的首地址,把地址強(qiáng)制轉(zhuǎn)換成char *類型后賦給char *類型的指針變量p。同時告訴我們這塊內(nèi)存將用來存儲char 類型的數(shù)據(jù)。也就是說你只能通過指針變量p 來操作這塊內(nèi)存。這塊內(nèi)存本身并沒有名字,對它的訪問是匿名訪問。

上面就是使用malloc 函數(shù)成功分配一塊內(nèi)存的過程。但是,每次你都能分配成功嗎?

不一定。上面的對話,皇帝讓戶部侍郎查詢是否還有足夠的良田未被分配出去。使用malloc函數(shù)同樣要注意這點(diǎn):如果所申請的內(nèi)存塊大于目前堆上剩余內(nèi)存塊(整塊),則內(nèi)存分配會失敗,函數(shù)返回NULL。注意這里說的“堆上剩余內(nèi)存塊”不是所有剩余內(nèi)存塊之和,因?yàn)閙alloc 函數(shù)申請的是連續(xù)的一塊內(nèi)存。

既然malloc 函數(shù)申請內(nèi)存有不成功的可能,那我們在使用指向這塊內(nèi)存的指針時,必須用if(NULL != p)語句來驗(yàn)證內(nèi)存確實(shí)分配成功了。

3、用malloc 函數(shù)申請0 字節(jié)內(nèi)存

另外還有一個問題:用malloc 函數(shù)申請0 字節(jié)內(nèi)存會返回NULL 指針嗎?

可以測試一下,也可以去查找關(guān)于malloc 函數(shù)的說明文檔。申請0 字節(jié)內(nèi)存,函數(shù)并不返回NULL,而是返回一個正常的內(nèi)存地址。但是你卻無法使用這塊大小為0 的內(nèi)存。這好尺子上的某個刻度,刻度本身并沒有長度,只有某兩個刻度一起才能量出長度。對于這一點(diǎn)一定要小心,因?yàn)檫@時候if(NULL != p)語句校驗(yàn)將不起作用。

4、內(nèi)存釋放

既然有分配,那就必須有釋放。不然的話,有限的內(nèi)存總會用光,而沒有釋放的內(nèi)存卻在空閑。與malloc 對應(yīng)的就是free 函數(shù)了。free 函數(shù)只有一個參數(shù),就是所要釋放的內(nèi)存塊的首地址。比如上例: free(p); free 函數(shù)看上去挺狠的,但它到底作了什么呢?其實(shí)它就做了一件事:斬斷指針變量與這塊內(nèi)存的關(guān)系。比如上面的例子,我們可以說malloc 函數(shù)分配的內(nèi)存塊是屬于p 的,因?yàn)槲覀儗@塊內(nèi)存的訪問都需要通過p 來進(jìn)行。free 函數(shù)就是把這塊內(nèi)存和p 之間的所有關(guān)系斬斷。從此p 和那塊內(nèi)存之間再無瓜葛。至于指針變量p 本身保存的地址并沒有改變,但是它對這個地址處的那塊內(nèi)存卻已經(jīng)沒有所有權(quán)了。那塊被釋放的內(nèi)存里面保存的值也沒有改變,只是再也沒有辦法使用了。

這就是free 函數(shù)的功能。按照上面的分析,如果對p 連續(xù)兩次以上使用free 函數(shù),肯定會發(fā)生錯誤。因?yàn)榈谝皇褂胒ree 函數(shù)時,p 所屬的內(nèi)存已經(jīng)被釋放,第二次使用時已經(jīng)無內(nèi)存可釋放了。關(guān)于這點(diǎn),我上課時讓學(xué)生記住的是:一定要一夫一妻制,不然肯定出錯。

malloc 兩次只free 一次會內(nèi)存泄漏;malloc 一次free 兩次肯定會出錯。也就是說,在程序中malloc 的使用次數(shù)一定要和free 相等,否則必有錯誤。這種錯誤主要發(fā)生在循環(huán)使用malloc 函數(shù)時,往往把malloc 和free 次數(shù)弄錯了。這里留個 練習(xí):

寫兩個函數(shù),一個生成鏈表,一個釋放鏈表。兩個函數(shù)的參數(shù)都只使用一個表頭指針。

5、內(nèi)存釋放之后

既然使用free 函數(shù)之后指針變量p 本身保存的地址并沒有改變,那我們就需要重新把p的值變?yōu)镹ULL: p = NULL; 這個NULL 就是我們前面所說的“栓野狗的鏈子”。如果你不栓起來遲早會出問題的。比如:在free(p)之后,你用if(NULL != p)這樣的校驗(yàn)語句還能起作用嗎?例如:

詳談C語言內(nèi)存錯誤的六條相關(guān)例子及解決對策

釋放完塊內(nèi)存之后,沒有把指針置NULL,這個指針就成為了“野指針”,也有書叫“懸垂指針”。這是很危險的,而且也是經(jīng)常出錯的地方。所以一定要記住一條:free 完之后,一定要給指針置NULL。

同時留一個問題:對NULL 指針連續(xù)free 多次會出錯嗎?為什么?如果讓你來設(shè)計free函數(shù),你會怎么處理這個問題?

六、內(nèi)存已經(jīng)被釋放了,但是繼續(xù)通過指針來使用

這里一般有三種情況:

第一種:就是上面所說的,free(p)之后,繼續(xù)通過p 指針來訪問內(nèi)存。解決的辦法就是給p 置NULL。

第二種:函數(shù)返回棧內(nèi)存。這是初學(xué)者最容易犯的錯誤。比如在函數(shù)內(nèi)部定義了一個數(shù)組,卻用return 語句返回指向該數(shù)組的指針。解決的辦法就是弄明白棧上變量的生命周期。

第三種:內(nèi)存使用太復(fù)雜,弄不清到底哪塊內(nèi)存被釋放,哪塊沒有被釋放。解決的辦法是重新設(shè)計程序,改善對象之間的調(diào)用關(guān)系。

上面詳細(xì)討論了常見的六種錯誤及解決對策,希望讀者仔細(xì)研讀,盡量使自己對每種錯誤發(fā)生的原因及預(yù)防手段爛熟于胸。一定要多練,多調(diào)試代碼,同時多總結(jié)經(jīng)驗(yàn)。
責(zé)編AJX

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3034

    瀏覽量

    74135
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7608

    瀏覽量

    137110
  • 源代碼
    +關(guān)注

    關(guān)注

    96

    文章

    2946

    瀏覽量

    66799
收藏 人收藏

    評論

    相關(guān)推薦

    C語言常見的錯誤及解決對策

    定義了指針變量,但是沒有為指針分配內(nèi)存,即指針沒有指向一塊合法的內(nèi)存。淺顯的例子就不舉了,這里舉幾個比較隱蔽的例子。
    發(fā)表于 10-20 10:09 ?3358次閱讀

    C程序中常見的與內(nèi)存相關(guān)錯誤

    C語言入門程序員來說,管理和使用虛擬存儲器可能是個困難的,容易出錯的任務(wù)。與存儲器有關(guān)的錯誤屬于那些最令人驚恐的錯誤,因?yàn)樗鼈兘?jīng)常在時間和空間上,都在距
    發(fā)表于 06-14 17:13 ?361次閱讀
    <b class='flag-5'>C</b>程序中常見的與<b class='flag-5'>內(nèi)存</b><b class='flag-5'>相關(guān)</b>的<b class='flag-5'>錯誤</b>

    三星開建第六條代工芯片生產(chǎn)線 生產(chǎn)5納米芯片

    三星電子公司周四表示,其在韓國的第六條代工芯片生產(chǎn)線已于本月較早時破土動工,計劃明年下半年開始生產(chǎn),將生產(chǎn)邏輯芯片,以減少其對波動較大的存儲芯片部門的依賴。
    的頭像 發(fā)表于 05-21 11:43 ?4437次閱讀

    STM8系列C語言程序例子和易錯分析

    一些關(guān)于STM8芯片的C語言程序例子以及容易出現(xiàn)的錯誤,主要針對于寄存器的相關(guān)配置,對于想了解STM8庫函數(shù)的工程師可能有一定的幫助 全文
    發(fā)表于 07-02 05:02

    Icc AVR C語言例子程序源程序

    Icc AVR C語言的應(yīng)用,本章例子程序編號為對應(yīng)第九章IARAVRC語言例子程序編號,這些例子
    發(fā)表于 07-01 00:23 ?184次下載

    C語言命語法錯誤大全

    c語言學(xué)習(xí)者必備,知道錯誤的意思會少很多煩惱
    發(fā)表于 03-13 16:44 ?15次下載

    Vincross開發(fā)出了一款機(jī)器人蜘蛛,還擁有六條

    這款機(jī)器人蜘蛛叫Hexa,意為“六條腿”。的確,雖然外形頗似蜘蛛,但這個機(jī)器人只有6腿,是個“四不像”。Hexa配備19個電機(jī)(關(guān)節(jié)多)、傳感器和人工智能軟件,它的六條腿十分敏捷,可以穿越各種
    發(fā)表于 07-03 14:40 ?1591次閱讀

    內(nèi)存條需要注意什么

    安裝新的內(nèi)存條時不要為追求高性能而安裝與原內(nèi)存條差距過大的內(nèi)存條。比如我們在1gb內(nèi)存條旁邊加裝4gb的內(nèi)存條,這樣是不行的。如果這樣安裝會
    的頭像 發(fā)表于 12-08 09:46 ?12.9w次閱讀

    干貨 | 嵌入式C語言內(nèi)存管理

    很多工程師都知道,C/C++語言與其他語言不同,它需要開發(fā)者自己管理內(nèi)存資源,動態(tài)內(nèi)存使用不當(dāng),
    的頭像 發(fā)表于 07-23 14:32 ?4937次閱讀

    C語言內(nèi)存錯誤的原因及解決辦法?

    定義了指針變量,但是沒有為指針分配內(nèi)存,即指針沒有指向一塊合法的內(nèi)存。淺顯的例子就不舉了,這里舉幾個比較隱蔽的例子。
    的頭像 發(fā)表于 08-31 14:42 ?3190次閱讀

    技術(shù)貼:常見的C語言內(nèi)存錯誤對策

    一、指針沒有指向一塊合法的內(nèi)存 定義了指針變量,但是沒有為指針分配內(nèi)存,即指針沒有指向一塊合法的內(nèi)存。淺顯的例子就不舉了,這里舉幾個比較隱蔽的例子
    的頭像 發(fā)表于 10-30 09:55 ?301次閱讀

    C語言常見內(nèi)存錯誤及解決方法

      本文將帶您了解一些良好的和內(nèi)存相關(guān)的編碼實(shí)踐,以將內(nèi)存錯誤保持在控制范圍內(nèi)。內(nèi)存錯誤
    的頭像 發(fā)表于 02-14 13:10 ?3309次閱讀

    c語言代碼錯誤怎么找

    當(dāng)我們編寫C語言代碼時,常常會遇到一些錯誤。這些錯誤可能是語法錯誤,邏輯錯誤或者是運(yùn)行時
    的頭像 發(fā)表于 11-24 10:05 ?4178次閱讀

    C語言內(nèi)存泄漏問題原理

    內(nèi)存泄漏問題只有在使用堆內(nèi)存的時候才會出現(xiàn),棧內(nèi)存不存在內(nèi)存泄漏問題,因?yàn)闂?b class='flag-5'>內(nèi)存會自動分配和釋放。C
    發(fā)表于 03-19 11:38 ?540次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b><b class='flag-5'>內(nèi)存</b>泄漏問題原理

    使用C語言實(shí)現(xiàn)的CRC計算單元的例子

    使用C語言實(shí)現(xiàn)的CRC計算單元的例子
    的頭像 發(fā)表于 05-16 16:16 ?1022次閱讀