C/C++自主分配出現(xiàn)double free or corruption問(wèn)題解決
引言
寫過(guò) C/C++ 的都知道,內(nèi)存允許程序員自主分配,用完了這些資源也得釋放出來(lái),這種在系統(tǒng)運(yùn)行過(guò)程中動(dòng)態(tài)申請(qǐng)的內(nèi)存,稱為動(dòng)態(tài)內(nèi)存。
常言道,借東西好借好還,下次再借也不難,但是有的人有時(shí)候還真的忘了還回去。這要是發(fā)生在程序運(yùn)行時(shí),申請(qǐng)的內(nèi)存沒(méi)正常釋放,沒(méi)管理好,就避免不了會(huì)面對(duì)內(nèi)存報(bào)錯(cuò)的問(wèn)題。
內(nèi)存都允許你自由操縱了,靈活性是真的大,恰恰這也是它的弊端。
今天就來(lái)聊聊 C/C++ 的報(bào)錯(cuò) double free or corruption
怎么分配和釋放內(nèi)存?
C 語(yǔ)言提供了兩個(gè)函數(shù)用于分配和釋放內(nèi)存 malloc 和 free,需要引用頭文件 <stdlib.h>。<stdlib.h> 是 C 標(biāo)準(zhǔn)庫(kù)頭文件 為 C 語(yǔ)言程序員提供可靠、高效的函數(shù),以實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存分配、數(shù)據(jù)類型轉(zhuǎn)換、偽隨機(jī)數(shù)生成、過(guò)程控制、搜索和排序、數(shù)學(xué)以及多字節(jié)或?qū)捵址瘮?shù),還包括一些常用常數(shù),目的是促進(jìn)組織和平臺(tái)間的代碼標(biāo)準(zhǔn)化。
#include <stdlib.h> #include <stdio.h> int main() { int *ptr = malloc(sizeof(int)); *ptr = 100; printf("%d", *ptr); free(ptr); return 0; }
輸出:
100
調(diào)用 malloc 會(huì)分配一塊內(nèi)存空間,并將這塊內(nèi)存空間的首地址返回。調(diào)用時(shí),需要傳入目標(biāo)內(nèi)存空間的大小,單位按照字節(jié)(Byte)算,而返回的地址數(shù)據(jù)類型是 void*,所以,根據(jù)目標(biāo)空間的具體用途轉(zhuǎn)換即可。
這塊內(nèi)存空間在分配之后還屬于未初始化的狀態(tài),如果對(duì)內(nèi)存空間的使用比較復(fù)雜,建議先用 memset 初始化一下。
內(nèi)存空間使用完,需要使用 free 釋放掉,避免閑置浪費(fèi),否則就算是內(nèi)存泄漏了。內(nèi)存泄露會(huì)直到程序進(jìn)程結(jié)束為止。
在其它的高級(jí)語(yǔ)言里,比如 Java、Python 等,出于內(nèi)存安全的考慮,都不會(huì)允許用戶自己管理內(nèi)存,而 C++ 是個(gè)例外,這可能來(lái)自于 C 語(yǔ)言的傳承。
C++ 里同樣提供了 malloc 和 free,但是引用的頭文件變成了 <cstdlib>。<cstdlib>是 <stdlib.h> 增強(qiáng)版,而且所有內(nèi)容都在命名空間內(nèi)聲明,所以使用前必須通過(guò)命名空間引用。
另外 C++ 還提供了兩個(gè)額外的操作符用于分配和釋放內(nèi)存,分別是 new 和 delete。
#include <iostream> using namespace std; int main() { int *ptr = new int; *ptr = 100; cout << *ptr << endl; delete ptr; return 0; }
輸出:
100
關(guān)鍵詞 new 后接上一個(gè)數(shù)據(jù)類型,然后分配和數(shù)據(jù)類型 int 對(duì)應(yīng)大小的內(nèi)存空間,并返回首地址。對(duì)應(yīng)地,new 申請(qǐng)的內(nèi)存空間被使用完不再需要時(shí),應(yīng)該使用關(guān)鍵詞 delete 釋放,delete 直接操作內(nèi)存空間首地址。
出現(xiàn) double free or corruption Error
借來(lái)的錢用得可以很爽,是的,常人都這樣。不過(guò),每到要還錢的時(shí)候就特別不情愿,要么推三推四,要么直接抵賴,一不留神就忘了是否有還過(guò)這事。
比如,張三本來(lái)一直在外租房將就著過(guò)日子,隨著家里人口逐漸增多,就和老婆合計(jì)著從銀行貸了一筆資金準(zhǔn)備買房嘛,貸了款之后,銀行貸款經(jīng)理就告訴他,“張先生,你們家以后每月就得由一名代表人來(lái)還貸款,不需要幾個(gè)人同時(shí)還的,記住了哈!”
好了,這個(gè)故事給了我們什么啟發(fā)呢?就是資金或者資源的借入借出需要有一個(gè)管理人,這樣可以避免混亂進(jìn)而出錯(cuò)。
同樣的,在 C/C++ 的編程里邊,經(jīng)常會(huì)出現(xiàn)一些內(nèi)存資源管理混亂而出現(xiàn)的報(bào)錯(cuò)甚至運(yùn)行時(shí)崩潰的問(wèn)題,比如 double free or corruption。
#include <iostream> using namespace std; int main() { int *ptr = new int; *ptr = 100; cout << *ptr << endl; delete ptr; delete ptr; return 0; }
執(zhí)行
100 free(): double free detected in tcache 2 Aborted (core dumped)
程序執(zhí)行崩潰并報(bào)錯(cuò) double free,根本原因是對(duì)同一內(nèi)存地址調(diào)用了多次的 free 或 delete 執(zhí)行釋放,這會(huì)導(dǎo)致應(yīng)用的內(nèi)存管理數(shù)據(jù)結(jié)構(gòu)被損壞,甚至?xí)试S惡意用戶在內(nèi)存任意區(qū)域?qū)懭霐?shù)據(jù)。這類損壞會(huì)導(dǎo)致程序崩潰或者程序的部分執(zhí)行流程被改變。如果攻擊者這個(gè)時(shí)候特意覆蓋特定的寄存器或者內(nèi)存區(qū)域來(lái)引導(dǎo)執(zhí)行他們的代碼,進(jìn)而可以產(chǎn)生提升權(quán)限的交互式 shell,這樣就完全被破防了。
這也算是內(nèi)存泄漏的一種,系統(tǒng)一旦檢測(cè)到 double free 也會(huì)終止進(jìn)程繼續(xù)執(zhí)行(Aborted)。
內(nèi)存被釋放之后會(huì)發(fā)生什么?
一塊內(nèi)存被釋放之后,空閑的內(nèi)存會(huì)被放入鏈表中,用于重新管理和組合不同的空閑內(nèi)存碎片,便于將來(lái)用于分配更大的內(nèi)存空間。這個(gè)鏈表屬于雙向鏈表,每個(gè)空閑的內(nèi)存空間都可以往前和往后查找其它內(nèi)存空間。
那么攻擊者可以利用這個(gè)過(guò)程嗎?
答案是肯定的。當(dāng) free 被調(diào)用時(shí),攻擊者可以讓原本需要被鏈表管理的空閑內(nèi)存取消鏈接,覆蓋寄存器值并從緩沖區(qū)載入shell代碼,最終往內(nèi)存寫入任意值。
常見的觸發(fā)情形
上面的示例代碼簡(jiǎn)單演示了 double free 的觸發(fā),平常出現(xiàn)這種報(bào)錯(cuò)的條件并不比上面的情形要復(fù)雜多少。比如,釋放同一塊內(nèi)存的動(dòng)作在相隔了幾百甚至更多行的位置執(zhí)行,有的還發(fā)生在不同源碼文件,這就會(huì)讓程序員容易多次釋放。下面嘗試總結(jié)一下,來(lái)看一下常見的犯錯(cuò)情形:
- 釋放前判斷的條件錯(cuò)誤或者其它不常見的情況
- 內(nèi)存被釋放后還在使用
- 內(nèi)存釋放的管理責(zé)任方混亂
如何避免
其實(shí),細(xì)看一下上面總結(jié)的幾種常見犯錯(cuò)情形,我們也可以很好地避免低級(jí)錯(cuò)誤。
有個(gè)最佳實(shí)踐是,分配的內(nèi)存地址存儲(chǔ)變量 ptr 在定義聲明時(shí)就應(yīng)該初始化為 NULL,內(nèi)存被釋放后應(yīng)立刻將 ptr 置為 NULL,使用這塊內(nèi)存或者釋放前應(yīng)該遵循先判斷內(nèi)存空間是否有效的原則,簡(jiǎn)單點(diǎn)可以用 (ptr != NULL)。
另外,負(fù)責(zé)釋放的管理責(zé)任方應(yīng)該盡量單一,即使橫跨多個(gè)源文件或模塊。這里有個(gè)道理就是避免”多龍治水“。
中國(guó)在過(guò)去一直是個(gè)農(nóng)業(yè)大國(guó),有著重農(nóng)輕商的歷史,各種典故都有著農(nóng)業(yè)的影子。
相傳,幾龍治水、幾牛耕地那是對(duì)當(dāng)年農(nóng)業(yè)收成的預(yù)示,不妨翻一下老黃歷看看?“龍”是管雨的神,以五龍治水可獲風(fēng)調(diào)雨順,因東南西北中都有神龍,各施其職。龍少了當(dāng)年就要發(fā)大水;龍多了當(dāng)年將要天大旱。原因是管雨的龍神少了怕管不過(guò)來(lái),就忙忙碌碌四處播雨以至大澇;管雨的龍神多了呢,就像“三個(gè)和尚無(wú)水吃”一樣以至大旱。至于澇到什么程度還看治水的龍少到什么程度,龍?jiān)缴贊车迷絿?yán)重。旱的程度亦一樣。
因此就有了“龍多不下雨”的諺語(yǔ)。
計(jì)算機(jī)編程說(shuō)到底還是程序員的思維體現(xiàn),人情世故也會(huì)反映在代碼的邏輯上。
以上就是C/C++出現(xiàn)double free or corruption問(wèn)題解決的詳細(xì)內(nèi)容,更多關(guān)于C/C++ double free or corruption的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于在MFC中將窗口最小化到托盤實(shí)現(xiàn)原理及操作步驟
最小化的原理:首先要將窗口隱藏,然后在右下角繪制圖標(biāo);恢復(fù)的原理:將窗口顯示,再將托盤中的圖片刪除,接下來(lái)介紹實(shí)現(xiàn)方法,感興趣的朋友可以了解下啊,希望本文對(duì)你有所幫助2013-01-01深入分析C語(yǔ)言中結(jié)構(gòu)體指針的定義與引用詳解
本篇文章是對(duì)C語(yǔ)言中結(jié)構(gòu)體指針的定義與引用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++ 內(nèi)存分配處理函數(shù)set_new_handler的使用
這篇文章主要介紹了C++ 內(nèi)存分配處理函數(shù)set_new_handler的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02深入C++中構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值操作符、析構(gòu)函數(shù)的調(diào)用過(guò)程總結(jié)
本篇文章是對(duì)C++中構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值操作符、析構(gòu)函數(shù)的調(diào)用過(guò)程進(jìn)行了總結(jié)與分析,需要的朋友參考下2013-05-05C++動(dòng)態(tài)內(nèi)存分配超詳細(xì)講解
給數(shù)組分配多大的空間?你是否和初學(xué)C時(shí)的我一樣,有過(guò)這樣的疑問(wèn)。這一期就來(lái)聊一聊動(dòng)態(tài)內(nèi)存的分配,讀完這篇文章,你可能對(duì)內(nèi)存的分配有一個(gè)更好的理解2022-08-08