vs使用未初始化的內存怎么解決_遇到C語言內存錯誤怎么辦?一定要找準這六個原因...

一、沒有為指針分配內存

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

a8ce57755d446bb3b22bfce92c46d5b8.png

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 指針分配了內存。

d4bef91a96e1d5785b340a31f1b10a51.png

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 版本被編譯器完全優化掉,這樣就不會影響代碼的性能。

03d802df5207dbf5419e6a4a56b3558d.png

有人也許會問,既然在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)這種寫法。這樣只會使你的代碼可移植性下降。

4af064605972dd3d39584c5e2d97a6a7.png

三、 內存越界

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

int a[10] = {0};

for (i=0; i<=10; i++)

{

a[i] = i;

}

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

9aa66c9c4cad6ba64103892072e19724.png

四、 給指針分配內存后,沒有初始化內存

犯這個錯誤往往是由于沒有初始化的概念或者是以為內存分配好之后其值自然為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 宏校驗失敗。

13e747948471020dd90d28568fe0a694.png

五、沒有釋放申請的內存,導致內存泄露

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

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

1、告老還鄉求良田

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

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

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

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

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

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

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

。。。。

fd6c80f5e9d06d75d6b255081c2af19a.png

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)語句校驗將不起作用。

9cc569b3e9b02aa36e848e542827232e.png

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函數,你會怎么處理這個問題?

51008a24a0f8a737ffe3c4dc9cc8a1a0.png

六、內存已經被釋放了,但是繼續通過指針來使用

這里一般有三種情況:

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

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

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

20bfcabbe572c1b0f08eff2ed0a35e3d.png

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

最后,如果你想更好的提升你的編程能力,好好學習C/C++編程知識的話!那么你很幸運~

點擊下方“了解更多”加入C語言/C++企鵝圈,這里還有一些你可能不知道的趣事分享喲。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/533831.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/533831.shtml
英文地址,請注明出處:http://en.pswp.cn/news/533831.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux服務器如何設置雙網卡,linux服務器設置(雙網卡共享上網)

一、網絡拓補結構&#xff1a;服務器&#xff1a;兩網卡的設置&#xff1a;eth0:202.96.168.100 掩碼&#xff1a;255.255.255.0 網關&#xff1a;202.96.168.68 #與 Inte.Net 相聯eth1:192.168.1.1掩碼&#xff1a;255.255.255.0#與局域網相聯客戶機子網段&#xff1a;192.1…

pwn環境搭建_pwndbg、pwntools環境搭建(Unix系統)

目錄[TOC]pwndbg環境搭建項目地址https://github.com/pwndbg/pwndbg搭建過程1、安裝環境基礎gitpythonpython-pip2、安裝過程使用git命令克隆遠程項目到本地。git clone https://github.com/pwndbg/pwndbg進入項目根目錄并執行一鍵安裝腳本cd pwndbg && ./setup.sh該腳…

cad求和插件_黑科技 | 無BIM建模下平面CAD自動生成門窗表

如果你接到的施工圖既不是用天正出的&#xff0c;也不是用revit出的&#xff0c;還得統計門窗表&#xff0c;那么你需要讀完這篇文章。為了能夠讓自己和所有底層同行們從這項無腦又燒腦的機械勞動中解脫&#xff0c;C君近期利用茶余飯后的時間開發了一個小插件&#xff0c;可以…

linux數據庫實例開機啟動,linux下數據庫實例開機自啟動設置

linux下數據庫實例開機自啟動設置 1、修改/oratab [rootorg54 ~]# vi/etc/oratab --把N改為Y&#xff0c;如下提示 # This file is used by ORACLEutilities. It is created by root.sh # and updated by the Database ConfigurationAssistant when creating # a datablinux下數…

panic 蘋果aop_Go Web開發之Revel - 攔截器

一個攔截器是一個框架在調用action方法前或后調用的函數. 它允許一種AOP的形式, 它經常被用于做下面幾種事情:Request loggingError handlingStats keeping在Revel里, 一個攔截器能接受兩種形式:1. 函數攔截器: 一個函數滿足沒有訪問特定的應用程序Controller被調用在應用程序中…

make找不到linux內核函數,linux內核make menuconfig出錯

今天實驗剛從服務器上遷移過來的維護的linux 9260的內核&#xff0c;使用make menuconfig時出錯&#xff0c;報錯為&#xff1a;yongtaoyongtao-desktop:~/public/linux_release/linux-2.6.24$ make menuconfigHOSTCC scripts/kconfig/conf.oHOSTCC scripts/kconfig/kxgettex…

tensorboard ckpt pb 模型的輸出節點_算法工程化系列——模型固化

摘要基于tensorflow訓練的模型一般被保存為ckpt形式的文件&#xff0c;隨著當前深度學習模型網絡越來越大&#xff0c;對應模型也會非常大。當對外提供服務的時候&#xff0c;如果采用ckpt的形式&#xff0c;服務進程被調起來非常困難&#xff0c;且推理服務一般速度也較慢(會達…

深度linux內核升級,深度操作系統 2020.11.11 更新發布:內核升級

原標題&#xff1a;深度操作系統 2020.11.11 更新發布&#xff1a;內核升級IT之家11月11日消息 今日&#xff0c;深度操作系統宣布2020.11.11 更新現已發布。本次更新包括升級內核、Debian 10.6 倉庫以及系統安全性更新。系統安全方面&#xff0c;本次更新修復了 Firefox-ESR 安…

unity 使用mysql實現登錄注冊_用mysql實現登錄注冊功能

1、創建用戶表表結構如下idunameupwdisdelete注意&#xff1a;需要對密碼進行加密。如果使用md5加密&#xff0c;則密碼包含32個字符。如果使用sha1加密&#xff0c;則密碼包含40個字符&#xff0c;這里使用這種方式。md5加密方式&#xff1a;import hashlibpwd 123456my_md5 …

python爬電影_使用Python多線程爬蟲爬取電影天堂資源

最近花些時間學習了一下Python&#xff0c;并寫了一個多線程的爬蟲程序來獲取電影天堂上資源的迅雷下載地址&#xff0c;代碼已經上傳到GitHub上了&#xff0c;需要的同學可以自行下載。剛開始學習python希望可以獲得寶貴的意見。 先來簡單介紹一下&#xff0c;網絡爬蟲的基本實…

打不開磁盤配額linux,九度OJ 1455 珍惜現在,感恩生活 -- 動態規劃(背包問題)...

題目描述&#xff1a;為了挽救災區同胞的生命&#xff0c;心系災區同胞的你準備自己采購一些糧食支援災區&#xff0c;現在假設你一共有資金n元&#xff0c;而市場有m種大米&#xff0c;每種大米都是袋裝產品&#xff0c;其價格不等&#xff0c;并且只能整袋購買。請問&#xf…

erp 維護費 要交嗎_ERP系統維護費

今年8月&#xff0c;SAP中國公司宣布2009年1月1日前將由傳統支持服務轉向企業級支持服務(SAP Enterprise Support)。同時將開始實施漸進式定價方案&#xff0c;并預計在2012年之前&#xff0c;逐漸將所有客戶從現行的SAP Standard/Premium Support的定價協議過渡為SAP Enterpri…

sentinel 端口_Sentinel原理:控制臺是如何獲取到實時數據的

Sentinel 系列教程&#xff0c;現已上傳到 github 和 gitee 中&#xff1a;GitHub&#xff1a;https://github.com/all4you/sentinel-tutorialGitee&#xff1a;https://gitee.com/all_4_you/sentinel-tutorialSentinel 能夠被大家所認可&#xff0c;除了他自身的輕量級&#x…

linux桌面時區設置,如何在Ubuntu 20.04上設置或更改時區

對于許多與系統相關的任務和進程&#xff0c;使用正確的時區至關重要。 例如&#xff0c;cron守護程序使用系統的時區執行cron作業&#xff0c;而日志文件中的時間戳基于系統的同一時區。在Ubuntu上&#xff0c;系統的時區是在安裝過程中設置的&#xff0c;但以后可以輕松更改。…

ironpython2.7.9_IronPython下載

IronPython是一種在 .NET 及 Mono上的 Python 實現&#xff0c;由微軟的 Jim Hugunin所發起&#xff0c;是一個開源的項目&#xff0c;基于微軟的DLR引擎&#xff1b;托管于微軟的開源網站 CodePlex。IronPython 的官方并未實現 Python通用類庫&#xff0c;僅實現了部分核心類。…

python 最小二乘回歸 高斯核_「機器學習」一文讀懂線性回歸、嶺回歸和Lasso回歸...

點擊上方藍色字體&#xff0c;關注AI小白入門喲作者 | 文杰編輯 | yuquanle本文介紹線性回歸模型&#xff0c;從梯度下降和最小二乘的角度來求解線性回歸問題&#xff0c;以概率的方式解釋了線性回歸為什么采用平方損失&#xff0c;然后介紹了線性回歸中常用的兩種范數來解決過…

天宮初級認證答案_跨境電商人才初級認證試題以及答案

跨境電商人才初級認證試題以及答案跨境電商人才初級認證試題一&#xff0c;單選題(共40題,每題1分,共40分)1.在拍攝反光性產品時,就是從哪個角度進行拍攝的A、正面B、側面參考答案:B2.信用證就是一種( )信用A、商業B、銀行C、民間D、企業參考答案:B3、阿里巴巴專業術語中,MA的全…

Linux打包軟件版本帶時間,帶你寫一個 linux 下的打包軟件 tar

相信你對 linux 的 .tar.gz 有點熟悉&#xff0c;這就是先 tar 打包(.tar 后綴)&#xff0c;再對此 tar 文件用 gzip 壓縮(.tar.gz)的后綴名。值得注意的是&#xff0c; tar 不是壓縮軟件&#xff0c;它只做把一堆文件/文件夾打包到一個文件(tar 文件)里的事情&#xff0c;而文…

優先隊列默認是小頂堆嗎_一分鐘帶你讀懂什么是堆?

堆其實就是一種特殊的隊列——優先隊列。 普通的隊列游戲規則很簡單&#xff1a;就是先進先出&#xff1b;但這種優先隊列搞特殊&#xff0c;不是按照進隊列的時間順序&#xff0c;而是按照每個元素的優先級來比拼&#xff0c;優先級高的在堆頂。 這也很容易理解吧&#xff0c;…

螺旋測微器b類不確定度_物理實驗直測量不確定度評估.ppt

物理實驗直測量不確定度評估直接測量不確定度評估 Gauss分布 測量列的平均值、標準差 A類不確定度 t分布 B類不確定度 直接測量的合成不確定度 Gauss分布 也稱正態分布。 δ的平均值等于0、方差為σ。 特征&#xff1a; 對稱性——大于平均值與小于平均值的概率相等&#xff1b…