C語言深度剖析書籍學習記錄 第五章 內存管理

?常見的內存錯誤

  • 定義了指針變量,但是沒有為指針分配內存,即指針沒有指向一塊合法的內存。

結構體成員指針未初始化

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

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

  • 不管什么時候,我們使用指針之前一定要確保指針是有效的。
  • 一般在函數入口處使用 assert(NULL != p)對參數進行校驗。在非參數的地方使用 if(NULL != p)來校驗。但這都有一個要求,即 p 在定義的同時被初始化為 NULL 了。比 如上面的例子,即使用 if(NULL != p)校驗也起不了作用,因為 name 指針并沒有被初始 化為 NULL,其內部是一個非 NULL 的亂碼。?
  • 使用指針之前需要對其賦值為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。未初始化指針變量也許看起來不那么嚴重,但是它確確實實是個非常嚴重的問題,而且往往 出現這種錯誤很難找到原因。
  • 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。
  • 至于指針變量如果未被初始化,會導致 if 語句或 assert 宏校驗失敗。因為指針指向數據存放的是亂碼,但是系統認為有數據是正確的,所以不對其存儲的數據和默認的數值進行比較就無法判定數據的有效性

內存越界

  • 內存分配成功,且已經初始化,但是操作越過了內存的邊界。 這種錯誤經常是由于操作數組或指針時出現“多 1”或“少 1”,常發生于循環遍歷
  • 所以,for 循環的循環變量一定要使用半開半閉的區間,而且如果不是特殊情況,循環變 量盡量從 0 開始

內存泄露

  • 會產生泄漏的內存就是堆上的內存(這里不討論資源或句柄等泄漏情況),也就是說由 malloc 系列函數或 new 操作符分配的內存。如果用完之后沒有及時 free 或 delete,這塊內存 就無法釋放,直到整個程序終止
  • (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)語句來驗證內存確實分配成功了。

使用malloc函數申請 0 字節內存

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

內存釋放

  • 既然有分配,那就必須有釋放。不然的話,有限的內存總會用光,而沒有釋放的內存 卻在空閑。與 malloc 對應的就是 free 函數了。free 函數只有一個參數,就是所要釋放的內 存塊的首地址。
  • 比如上例:? ?free(p);
  • free 函數看上去挺狠的,但它到底作了什么呢?其實它就做了一件事:斬斷指針變量與這塊內存的關系。比如上面的例子,我們可以說 malloc 函數分配的內存塊是屬于 p 的,因 為我們對這塊內存的訪問都需要通過 p 來進行。free 函數就是把這塊內存和 p 之間的所有關系斬斷。從此 p 和那塊內存之間再無瓜葛。至于指針變量 p 本身保存的地址并沒有改變, 但是它對這個地址處的那塊內存卻已經沒有所有權了。那塊被釋放的內存里面保存的值也沒有改變,只是再也沒有辦法使用了。
    這就是 free 函數的功能。
  • 按照上面的分析,如果對 p 連續兩次以上使用 free 函數,肯 定會發生錯誤。因為第一使用 free 函數時,p 所屬的內存已經被釋放,第二次使用時已經無 內存可釋放了。關于這點,我上課時讓學生記住的是:一定要一夫一妻制,不然肯定出錯。內存釋放之后需要將指針重新設置為NULL

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

  • 這里一般有三種情況:
  • 第一種:就是上面所說的,free(p)之后,繼續通過 p 指針來訪問內存。解決的辦法 就是給 p 置 NULL。
  • 第二種:函數返回棧內存。這是初學者最容易犯的錯誤。比如在函數內部定義了一個 數組,卻用 return 語句返回指向該數組的指針。解決的辦法就是弄明白棧上變量的生命周期。
  • 第三種:內存使用太復雜,弄不清到底哪塊內存被釋放,哪塊沒有被釋放。解決的辦 法是重新設計程序,改善對象之間的調用關系。
請使用手機"掃一掃"x

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

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

相關文章

怎么改電腦網絡ip地址_拋棄重啟路由器獲取ip地址方式,巧妙運用ip代理改IP工具...

網絡是簡單的也是復雜的,在如此龐大的網絡世界里有太多的不確定因素,導致我們遇到IP限制問題,從而影響到我們的網絡訪問,而大家都知道,如果遇到ip被限制的問題,最快速直接的辦法就是把被限制的ip更換一個新…

C語言深度剖析書籍學習記錄 第六章 函數

函數的好處 1、降低復雜性:使用函數的最首要原因是為了降低程序的復雜性,可以使用函數來隱含信息,從而使你不必再考慮這些信息。2、避免重復代碼段:如果在兩個不同函數中的代碼很相似,這往往意味著分解工作有誤。這時,應該把兩個…

如何把word分裝到兩個byte_如何核對兩個Word文檔的內容差別?同事加班半小時,我只花了30秒...

昨天下班前,老板突然發了兩份Word文檔過來,一份是原稿,還有一份是修訂稿,叫我們找出兩份文檔的內容差別之處,我只花了30秒就搞定了,然后準時下班!你想知道我是怎么操作的嗎?下面小源…

stm32f767中文手冊_ALIENTEK 阿波羅 STM32F767 開發板資料連載第五章 SYSTEM 文件夾

1)實驗平臺:alientek 阿波羅 STM32F767 開發板2)摘自《STM32F7 開發指南(HAL 庫版)》關注官方微信號公眾號,獲取更多資料:正點原子第五章 SYSTEM 文件夾介紹第三章,我們介紹了如何在 MDK5 下建立 STM32F7 工程。在這個新建的工程之…

手機安卓學習 內核開發

官網開源代碼 Documentation - MiCode/Xiaomi_Kernel_OpenSource - Sourcegraph Xiaomi 11T Pro GitHub - MiCode/Xiaomi_Kernel_OpenSource: Xiaomi Mobile Phone Kernel OpenSourceAndroid 開源項目 | Android Open Source Project google安卓官網 目錄概覽 參考…

vs 啟動調用的目標發生異常_如何解決不可測、異常場景的問題?

阿里QA導讀:在軟件研發過程中,發布前跨多個系統的聯調測試是不可或缺的一環,而在聯調過程中,經常會遇到一些比較棘手的困難,阻塞整個聯調進程。其中比較典型的有:第三方的研發節奏不一致,導致無…

Linux內核 scatterlist介紹

scatterlist 物理內存的散列表。通俗講,就是把一些分散的物理內存,以列表的形式組織起來 誕生背景 假設有三個模塊可以訪問memory:CPU、DMA控制器和某個外設。CPU通過MMU以虛擬地址(VA)的形式訪問memory;…

www.python123.org_python爬蟲-requests

Requests庫是目前常用且效率較高的爬取網頁的庫1.一個簡單的例子import requests #引入requests庫r requests.get("http://www.baidu.com")  #調用get方法獲取界面print(r.status_code)    #輸出狀態碼print(r.text)    #輸出頁面信息通過以下代碼&#x…

Linux內核 crypto文件夾 密碼學知識學習

密碼算法分類 對稱算法非對稱算法消息摘要(單向哈希)算法這些算法作為加密函數框架的最底層,提供加密和解密的實際操作。這些函數可以在內核crypto文件夾下,相應的文件中找到。不過內核模塊不能直接調用這些函數,因為…

python隨機出100道加法題_自動出題隨機100題-20以內加減法全部算式

班 級:姓 名:12-819-411-1114-1018-111417-261215-113-417-819-1914-341516-31269619-161159312817-014-1414-1112-501414-017-616-111-012-211520-711113051019-1810619-691118-1220-519-818018114-1416-712-1015-1319-916-714-920-717-118-1611-815-416-1014-919-416-1413-…

Linux crypto相關知識的匯總 Linux加密框架crypto中的算法和算法模式(一)

Linux加密框架中的算法和算法模式 Linux加密框架中的算法和算法模式(一)_家有一希的博客-CSDN博客 加密框架支持的密碼算法主要是對稱密碼算法和哈希算法,暫時不支持非對稱密碼算法。除密碼算法外,加密框架還包括偽隨機數生成算法…

python3.5.2安裝pygame_【閑來無事,py寫game】Mac-Python3.5安裝pygame 1.9.2 小計

13正文之前沒錯,我就是這么不學無術,C實在學的雞兒疼,所以干脆搞點娛樂措施,昨天趕上了京東圖書做大活動,所以屯了一批書,好久沒碰python了。所以就整本玩玩!今天這不就上手了么!自己…

Linux crypto相關知識的匯總 Linux加密框架crypto對稱算法和哈希算法加密模式

參考鏈接 Linux加密框架中的算法和算法模式(二)_家有一希的博客-CSDN博客 對稱算法 分組算法模式 ECB模式 ECB模式下,明文數據被分為大小合適的分組,然后對每個分組獨立進行加密或解密如下圖所示如果兩個明文塊相同&#xff0c…

物化視圖和視圖的最大區別_基于catalyst的物化視圖改寫引擎的實現

更新日志:1. 2020/06/16 group by 視圖的部分描述錯誤,已修正。什么是物化視圖我先用我的話解釋一下什么是物化視圖。假設我們已經有A,B兩張表,現在我創建了一張表C,C是由A,B兩張表經過一條SQL處理得到的,這個時候我們…

Linux加密框架中的算法和算法模式

參考鏈接 Linux加密框架中的算法和算法模式(三)_家有一希的博客-CSDN博客 對稱算法 14 如上所示,在arc4.c中定義了兩個與RC4算法相關的算法實現,分別為arc4和ecb(arc4),其中arc4是RC算法的算法實現,而ecb…

python學籍管理系統 flask_taskday05-Python之flask學習 web開發最基本的需要(特別詳細且適用)...

1.首先一個Flask的Web項目的創建需求一(文章概述):一:必須實現命令工具管理App,用于在命令行輸入命令對項目進行管理,對后期多多益善二:必須實現“藍圖”管理,用于將app啟動函數與路由分開管理,…

Linux加密框架crypto AES代碼相關

例子 aes_generic.c - crypto/aes_generic.c - Linux source code (v5.15.11) - Bootlin static struct crypto_alg aes_alg {.cra_name "aes",.cra_driver_name "aes-generic",.cra_priority 100,.cra_flags CRYPTO_ALG_TYPE_CIPHER,.cra_blocks…

python語言print函數_Python 的 print 函數

Python 2.x 系列已經停止維護了, python 3.x 系列正在成為主流,盡管有些項目還是python2.x 的,之后寫Python 代碼為了保持兼容性,還是盡量和Python 3 標準保持一致作為一個Python newbee 而言, python 2.x 和 3.x 的 …

Linux加密框架crypto crypto_alg|cipher_alg數據結構|AES例子

加密框架將算法的屬性抽象為算法說明數據結構struct crypto_alg,加密框架中的每一個算法(基礎算法和衍生算法)都表示為一個算法說明數據結構的實例,因此將struct crypto_alg稱為通用算法說明數據結構。后續章節中如無特殊說明&…