精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

技術貼:常見的C語言內存錯誤及對策

電子設計 ? 來源:電子設計 ? 作者:電子設計 ? 2020-10-30 09:55 ? 次閱讀

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

1、結構體成員指針未初始化
struct student
{
char *name;
int score;
}stu,*pstu;

int main()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
return 0;
}


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

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

int main()
{
pstu = (struct student*)malloc(sizeof(struct student));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}


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

2、沒有為結構體指針分配足夠的內存
int main()
{
pstu = (struct student*)malloc(sizeof(struct student*));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}


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

3、函數的入口校驗
不管什么時候,我們使用指針之前一定要確保指針是有效的。

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

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

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

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

二、為指針分配的內存太小
為指針分配了內存,但是內存大小不夠,導致出現越界錯誤。

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


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

char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));


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

char a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};


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

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

曾經有一個學生在寫一個 windows 程序時,想調用字庫的某個字體。而調用這個字庫需要填充一個結構體。他很自然的定義了一個結構體變量,然后把他想要的字庫代碼賦值給了相關的變量。但是,問題就來了,不管怎么調試,他所需要的這種字體效果總是不出來。我在檢查了他的代碼之后,沒有發現什么問題,于是單步調試。在觀察這個結構體變量的內存時,發現有幾個成員的值為亂碼。就是其中某一個亂碼惹得禍!因為系統會按照這個結構體中的某些特定成員的值去字庫中尋找匹配的字體,當這些值與字庫中某種字體的某些項匹配時,就調用這種字體。但是很不幸,正是因為這幾個亂碼,導致沒有找到相匹配的字體!因為系統并無法區分什么數據是亂碼,什么數據是有效的數據。只要有數據,系統就理所當然的認為它是有效的。

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

int i = 10;
char *p = (char *)malloc(sizeof(char));
但是往往這個時候我們還不確定這個變量的初值,這樣的話可以初始化為 0 或 NULL。

int i = 0;
char *p = NULL;
如果定義的是數組的話,可以這樣初始化:

int a[10] = {0};
或者用 memset 函數來初始化為 0:

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

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

四、內存越界
內存分配成功,且已經初始化,但是操作越過了內存的邊界。這種錯誤經常是由于操作數組或指針時出現“多 1”或“少 1”。比如:

int a[10] = {0};
for (i=0; i<=10; i++)
{
a[i] = i;
}
所以,for 循環的循環變量一定要使用半開半閉的區間,而且如果不是特殊情況,循環變量盡量從 0 開始。

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

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

1、告老還鄉求良田
怎么去理解這個內存分配和釋放過程呢?先看下面這段對話:

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

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

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

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

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

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

。。。。

2、如何使用 malloc 函數
不要莫名其妙,其實上面這段小小的對話,就是 malloc 的使用過程。malloc 是一個函數,專門用來從堆上分配內存。使用 malloc 函數需要幾個要求:

內存分配給誰?這里是把良田分配給某功臣。

分配多大內存?這里是分配一千畝。

是否還有足夠內存分配?這里是還有足夠良田分配。

內存的將用來存儲什么格式的數據,即內存用來做什么?

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

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

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

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

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

3、用 malloc 函數申請 0 字節內存
另外還有一個問題:用 malloc 函數申請 0 字節內存會返回 NULL 指針嗎?

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

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

這就是 free 函數的功能。按照上面的分析,如果對 p 連續兩次以上使用 free 函數,肯定會發生錯誤。因為第一使用 free 函數時,p 所屬的內存已經被釋放,第二次使用時已經無內存可釋放了。關于這點,我上課時讓學生記住的是:一定要一夫一妻制,不然肯定出錯。

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

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


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

char *p = (char *)malloc(100);
strcpy(p, “hello”);
free(p); /* p 所指的內存被釋放,但是 p 所指的地址仍然不變*/

if (NULL != p)
{
/* 沒有起到防錯作用*/
strcpy(p, “world”); /* 出錯*/
}
釋放完塊內存之后,沒有把指針置 NULL,這個指針就成為了“野指針”,也有書叫“懸垂指針”。這是很危險的,而且也是經常出錯的地方。所以一定要記住一條:free 完之后,一定要給指針置 NULL。

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

六、內存已經被釋放了,但是繼續通過指針來使用
這里一般有三種情況:

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

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

第三種:內存使用太復雜,弄不清到底哪塊內存被釋放,哪塊沒有被釋放。解決的辦法是重新設計程序,改善對象之間的調用關系。

上面詳細討論了常見的六種錯誤及解決對策,希望讀者仔細研讀,盡量使自己對每種錯誤發生的原因及預防手段爛熟于胸。一定要多練,多調試代碼,同時多總結經驗。

審核編輯 黃昊宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7598

    瀏覽量

    136197
收藏 人收藏

    評論

    相關推薦

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

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

    C程序中常見的與內存相關的錯誤

    C語言入門程序員來說,管理和使用虛擬存儲器可能是個困難的,容易出錯的任務。與存儲器有關的錯誤屬于那些最令人驚恐的錯誤,因為它們經常在時間和空間上,都在距
    發表于 06-14 17:13 ?346次閱讀
    <b class='flag-5'>C</b>程序中<b class='flag-5'>常見</b>的與<b class='flag-5'>內存</b>相關的<b class='flag-5'>錯誤</b>

    C程序中10個與內存有關的常見錯誤

    內存有關的錯誤,屬于那種最令人驚恐的錯誤。在時間和空間上,經常在距離錯誤源一段距離之后才表現出來。將錯誤的數據寫到
    發表于 06-20 10:41 ?670次閱讀

    15個常見C語言陷阱及其解決方法

    C語言是一種非常流行的編程語言,因為它簡單易學,且廣泛應用于各個領域。但是,由于C語言本身的特性,它也容易引起一些
    發表于 09-09 14:51 ?2428次閱讀

    入門——C語言常見錯誤#C語言

    C語言
    jf_49750429
    發布于 :2022年11月16日 15:36:30

    【原創】常見內存錯誤對策

    作者:蔡琰老師(張飛實戰電子高級工程師)對于用CC++除了考慮上層應用,還需要考慮底層的內存管理,或者說內存泄漏的問題。1、指針沒有指向一塊合法的
    發表于 08-24 11:34

    C語言入門教程-指針常見錯誤

    指針常見錯誤 錯誤 1:未初始化的指針一個最易犯的指針錯誤是試圖引用未初始化(因而指向的是無效地址)的指針。例如: int*p; *p=12;
    發表于 07-29 11:47 ?1105次閱讀

    C語言命語法錯誤大全

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

    干貨 | 嵌入式C語言內存管理

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

    C語言常見錯誤:數組越界及其避免方法

    所謂的數組越界,簡單地講就是指數組下標變量的取值超過了初始定義時的大小,導致對數組元素的訪問出現在數組的范圍之外,這類錯誤也是 C 語言程序中最常見
    的頭像 發表于 12-06 09:13 ?9746次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b><b class='flag-5'>常見</b><b class='flag-5'>錯誤</b>:數組越界及其避免方法

    C語言常見內存錯誤及解決方法

      本文將帶您了解一些良好的和內存相關的編碼實踐,以將內存錯誤保持在控制范圍內。內存錯誤C
    的頭像 發表于 02-14 13:10 ?3250次閱讀

    C語言常見問題

    C語言常見問題
    發表于 03-21 14:57 ?0次下載

    常用的解決內存錯誤的方法

    1. 內存管理功能問題 由于C++語言對內存有主動控制權,內存使用靈活和效率高,但代價是不小心使用就會導致以下內存
    的頭像 發表于 11-10 15:29 ?1453次閱讀
    常用的解決<b class='flag-5'>內存</b><b class='flag-5'>錯誤</b>的方法

    c語言代碼錯誤怎么找

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

    C語言內存泄漏問題原理

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