堆(dui)棧溢出一般是由什(shen)么原因(yin)導(dao)致的?
時間:2018-12-24 來源:華(hua)清遠(yuan)見
堆棧(zhan)(zhan)溢(yi)出(chu)一般(ban)都(dou)是由(you)堆棧(zhan)(zhan)越界(jie)(jie)訪問(wen)導致的(de)(de)。例如(ru)函(han)(han)數(shu)內(nei)(nei)局部變量(liang)(liang)數(shu)組越界(jie)(jie)訪問(wen),或者函(han)(han)數(shu)內(nei)(nei)局部變量(liang)(liang)使(shi)用過多,超出(chu)了操作系統(tong)為該進程分配的(de)(de)棧(zhan)(zhan)的(de)(de)大(da)小也會(hui)導致堆棧(zhan)(zhan)溢(yi)出(chu)。深度解析:
首先要區分清(qing)楚堆(dui)、棧(zhan)、堆(dui)棧(zhan)這幾個名詞。堆(dui)(heap)和(he)棧(zhan)(stack)是兩種不同(tong)的內(nei)存(cun)管理(li)機制:
1.堆
堆(dui)被稱為動(dong)態(tai)內存(cun)(cun),由堆(dui)管(guan)理(li)器(qi)(qi)(系統里的大人物,山高(gao)皇(huang)帝遠不用去管(guan)它)管(guan)理(li),程序(xu)中(zhong)可(ke)以(yi)使用malloc函(han)數來(向堆(dui)管(guan)理(li)器(qi)(qi))申請分配堆(dui)內存(cun)(cun),使用完后使用free函(han)數釋放(給(gei)堆(dui)管(guan)理(li)器(qi)(qi)回收)。堆(dui)內存(cun)(cun)的特點是:在程序(xu)運(yun)行(xing)過程中(zhong)才申請分配,在程序(xu)運(yun)行(xing)中(zhong)即釋放(因此(ci)稱為動(dong)態(tai)內存(cun)(cun)分配技術)。
2.棧
棧(zhan)是(shi)(shi)(shi)C語(yu)言使用(yong)的(de)一種(zhong)內(nei)(nei)存(cun)自動(dong)(dong)(dong)分配技術(注意(yi)是(shi)(shi)(shi)自動(dong)(dong)(dong),不是(shi)(shi)(shi)動(dong)(dong)(dong)態,這是(shi)(shi)(shi)兩(liang)個概念(nian)),自動(dong)(dong)(dong)指的(de)是(shi)(shi)(shi)棧(zhan)內(nei)(nei)存(cun)操作不用(yong)C程(cheng)序員(yuan)干預,而是(shi)(shi)(shi)自動(dong)(dong)(dong)分配自動(dong)(dong)(dong)回(hui)收的(de)。C語(yu)言中局部(bu)變(bian)量(liang)就分配在(zai)棧(zhan)上(shang),進入函數(shu)時(shi)(shi)局部(bu)變(bian)量(liang)需要的(de)內(nei)(nei)存(cun)自動(dong)(dong)(dong)分配,函數(shu)結束退(tui)出時(shi)(shi)局部(bu)變(bian)量(liang)對應的(de)內(nei)(nei)存(cun)自動(dong)(dong)(dong)釋放,整個過程(cheng)中程(cheng)序員(yuan)不需要人為(wei)干預。
堆棧(zhan)這(zhe)個詞(ci)純粹是(shi)(shi)用(yong)(yong)來坑(keng)人的(de)。堆就(jiu)是(shi)(shi)堆(heap),棧(zhan)就(jiu)是(shi)(shi)棧(zhan)(stack),根(gen)本沒有另外(wai)一種內(nei)存管理機制叫(jiao)堆棧(zhan)。大(da)多數時(shi)候(hou)有人說起堆棧(zhan),其(qi)(qi)實他(ta)想說的(de)是(shi)(shi)棧(zhan),以前早(zao)些(xie)的(de)時(shi)候(hou),這(zhe)方(fang)面的(de)命名并不是(shi)(shi)特(te)別準確。(別人說堆棧(zhan)的(de)時(shi)候(hou),大(da)家知(zhi)道他(ta)其(qi)(qi)實想說的(de)是(shi)(shi)棧(zhan)就(jiu)行了(le),自己就(jiu)不要再(zai)用(yong)(yong)這(zhe)個不準確的(de)詞(ci)了(le))。既然堆和(he)棧(zhan)都是(shi)(shi)用(yong)(yong)來管理內(nei)存的(de)機制,使用(yong)(yong)時(shi)就(jiu)有一定的(de)規則。無(wu)視規則的(de)錯(cuo)(cuo)誤使用(yong)(yong)(C語言設計時(shi)賦(fu)予了(le)程序(xu)員(yuan)很大(da)的(de)自由度,所以有些(xie)錯(cuo)(cuo)誤語言本身是(shi)(shi)不會檢查的(de),全(quan)憑程序(xu)員(yuan)自己把握。)就(jiu)可以導(dao)致一些(xie)內(nei)存錯(cuo)(cuo)誤,如(ru)內(nei)存泄(xie)漏、溢出錯(cuo)(cuo)誤等。
3.存泄漏
內存(cun)泄(xie)漏主要發生(sheng)在堆內存(cun)使(shi)(shi)用(yong)(yong)(yong)中(zhong)。譬如我們使(shi)(shi)用(yong)(yong)(yong)malloc申(shen)請了(le)內存(cun),使(shi)(shi)用(yong)(yong)(yong)過后(hou)并(bing)未釋放而(er)丟棄了(le)指向該內存(cun)的(de)指針(這(zhe)個(ge)(ge)指針是這(zhe)段內存(cun)的(de)唯一(yi)記錄,程(cheng)序(xu)中(zhong)釋放該段內存(cun)都靠這(zhe)個(ge)(ge)指針了(le)),那么這(zhe)段堆內存(cun)就泄(xie)漏掉了(le)(堆管理器以(yi)(yi)為程(cheng)序(xu)還在使(shi)(shi)用(yong)(yong)(yong),所(suo)(suo)以(yi)(yi)不會將(jiang)這(zhe)段內存(cun)再(zai)次分配(pei)給(gei)別的(de)程(cheng)序(xu))。必須等(deng)到這(zhe)個(ge)(ge)程(cheng)序(xu)徹(che)底退出后(hou),系統回收該程(cheng)序(xu)所(suo)(suo)使(shi)(shi)用(yong)(yong)(yong)的(de)所(suo)(suo)有資源(申(shen)請的(de)內存(cun),使(shi)(shi)用(yong)(yong)(yong)的(de)文件描述符等(deng))時這(zhe)些泄(xie)漏的(de)內存(cun)才重(zhong)新(xin)回到堆管理器的(de)懷(huai)抱。
內存溢出在堆(dui)和棧(zhan)中都(dou)有可(ke)能(neng)發(fa)生(sheng)。參見章(zhang)節示例(li)1_2_stack_overflow.c中的8個示例(li)函(han)數(shu),其中前三個函(han)數(shu)與堆(dui)溢出有關,后五個函(han)數(shu)與棧(zhan)溢出有關。
4.堆溢出
函數heap_overflow中(zhong)使(shi)用malloc申請了16字節(jie)動態內(nei)存,然后嘗試(shi)去讀寫這16個內(nei)存之中(zhong)的(de)第n個。三(san)個測(ce)試(shi)分別給n賦值9,99和9999999,得到(dao)的(de)結(jie)果很有意(yi)思(見(jian)程(cheng)序后面的(de)注釋,大家也(ye)可以(yi)自己(ji)編譯運行測(ce)試(shi)),現(xian)在我們來探討其中(zhong)的(de)原理。
n等于(yu)(yu)9的(de)(de)(de)(de)(de)(de)(de)時(shi)(shi)(shi)候(hou)沒(mei)什么(me)好說的(de)(de)(de)(de)(de)(de)(de),本(ben)(ben)該正確(que)(que)運行,這個(ge)(ge)(ge)(ge)(ge)相(xiang)信(xin)大(da)家沒(mei)有異議。n等于(yu)(yu)99的(de)(de)(de)(de)(de)(de)(de)時(shi)(shi)(shi)候(hou)······竟然也可(ke)以正確(que)(que)運行,這個(ge)(ge)(ge)(ge)(ge)相(xiang)信(xin)很多(duo)人就有點想不(bu)(bu)(bu)通(tong)了(le)。我(wo)們申請的(de)(de)(de)(de)(de)(de)(de)空間只(zhi)有16字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)啊,怎么(me)竟然還可(ke)以訪(fang)問(wen)(wen)第99個(ge)(ge)(ge)(ge)(ge)字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)空間呢(這就是(shi)(shi)(shi)(shi)所謂的(de)(de)(de)(de)(de)(de)(de)堆(dui)(dui)(dui)溢出(chu)訪(fang)問(wen)(wen))?這時(shi)(shi)(shi)候(hou)實際(ji)已經(jing)堆(dui)(dui)(dui)溢出(chu)了(le),但(dan)是(shi)(shi)(shi)(shi)為(wei)(wei)什么(me)結果(guo)沒(mei)有出(chu)錯呢?原(yuan)因在操作(zuo)(zuo)系(xi)(xi)統的(de)(de)(de)(de)(de)(de)(de)內(nei)(nei)存(cun)分(fen)配(pei)(pei)(pei)策略中(zhong)。譬如linux中(zhong)內(nei)(nei)存(cun)是(shi)(shi)(shi)(shi)按照頁(ye)(ye)(ye)(Page,一般是(shi)(shi)(shi)(shi)4K字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)一個(ge)(ge)(ge)(ge)(ge)頁(ye)(ye)(ye))來(lai)管(guan)理(li)的(de)(de)(de)(de)(de)(de)(de),操作(zuo)(zuo)系(xi)(xi)統給(gei)(gei)(gei)進(jin)程分(fen)配(pei)(pei)(pei)內(nei)(nei)存(cun)本(ben)(ben)質上都是(shi)(shi)(shi)(shi)以頁(ye)(ye)(ye)為(wei)(wei)單位進(jin)行的(de)(de)(de)(de)(de)(de)(de)。也就是(shi)(shi)(shi)(shi)說你(ni)雖然只(zhi)要求(qiu)了(le)16個(ge)(ge)(ge)(ge)(ge)字(zi)(zi)(zi)(zi)節(jie)(jie)(jie),但(dan)是(shi)(shi)(shi)(shi)實際(ji)分(fen)配(pei)(pei)(pei)給(gei)(gei)(gei)你(ni)這個(ge)(ge)(ge)(ge)(ge)進(jin)程的(de)(de)(de)(de)(de)(de)(de)可(ke)能(neng)(neng)是(shi)(shi)(shi)(shi)一個(ge)(ge)(ge)(ge)(ge)頁(ye)(ye)(ye)(4K字(zi)(zi)(zi)(zi)節(jie)(jie)(jie))。這個(ge)(ge)(ge)(ge)(ge)頁(ye)(ye)(ye)中(zhong)只(zhi)有這16個(ge)(ge)(ge)(ge)(ge)字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)是(shi)(shi)(shi)(shi)你(ni)自己(ji)的(de)(de)(de)(de)(de)(de)(de)“合法(fa)財(cai)產”,其(qi)(qi)他(ta)(ta)(ta)(ta)部分(fen)你(ni)不(bu)(bu)(bu)該去(qu)訪(fang)問(wen)(wen)(一訪(fang)問(wen)(wen)就堆(dui)(dui)(dui)越(yue)界(jie)(jie))。但(dan)是(shi)(shi)(shi)(shi)因為(wei)(wei)操作(zuo)(zuo)系(xi)(xi)統對(dui)內(nei)(nei)存(cun)的(de)(de)(de)(de)(de)(de)(de)訪(fang)問(wen)(wen)權限管(guan)理(li)是(shi)(shi)(shi)(shi)以頁(ye)(ye)(ye)為(wei)(wei)單位的(de)(de)(de)(de)(de)(de)(de),因此本(ben)(ben)頁(ye)(ye)(ye)內(nei)(nei)16字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)之外的(de)(de)(de)(de)(de)(de)(de)內(nei)(nei)存(cun)你(ni)(非法(fa))訪(fang)問(wen)(wen)時(shi)(shi)(shi)系(xi)(xi)統仍(reng)然不(bu)(bu)(bu)會(hui)報錯,并且確(que)(que)實能(neng)(neng)夠達成目的(de)(de)(de)(de)(de)(de)(de)(示例(li)中(zhong)n等于(yu)(yu)99時(shi)(shi)(shi)讀寫仍(reng)然正確(que)(que))。那是(shi)(shi)(shi)(shi)不(bu)(bu)(bu)是(shi)(shi)(shi)(shi)說堆(dui)(dui)(dui)越(yue)界(jie)(jie)是(shi)(shi)(shi)(shi)無(wu)害的(de)(de)(de)(de)(de)(de)(de),完全不(bu)(bu)(bu)用擔心呢?顯然不(bu)(bu)(bu)是(shi)(shi)(shi)(shi)。因為(wei)(wei)堆(dui)(dui)(dui)越(yue)界(jie)(jie)最(zui)大(da)的(de)(de)(de)(de)(de)(de)(de)傷害不(bu)(bu)(bu)是(shi)(shi)(shi)(shi)對(dui)自己(ji),而(er)是(shi)(shi)(shi)(shi)對(dui)“別(bie)人”。因為(wei)(wei)除了(le)你(ni)申請的(de)(de)(de)(de)(de)(de)(de)16字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)外本(ben)(ben)頁(ye)(ye)(ye)面內(nei)(nei)其(qi)(qi)他(ta)(ta)(ta)(ta)內(nei)(nei)存(cun)可(ke)能(neng)(neng)會(hui)被堆(dui)(dui)(dui)管(guan)理(li)器(qi)(qi)分(fen)配(pei)(pei)(pei)給(gei)(gei)(gei)其(qi)(qi)他(ta)(ta)(ta)(ta)變量,你(ni)越(yue)界(jie)(jie)訪(fang)問(wen)(wen)時(shi)(shi)(shi)意味著你(ni)可(ke)能(neng)(neng)踐踏(ta)了(le)其(qi)(qi)他(ta)(ta)(ta)(ta)變量的(de)(de)(de)(de)(de)(de)(de)有效區域(譬如我(wo)們給(gei)(gei)(gei)第99個(ge)(ge)(ge)(ge)(ge)字(zi)(zi)(zi)(zi)節(jie)(jie)(jie)賦值為(wei)(wei)g時(shi)(shi)(shi),很可(ke)能(neng)(neng)把別(bie)處動(dong)態(tai)分(fen)配(pei)(pei)(pei)的(de)(de)(de)(de)(de)(de)(de)一個(ge)(ge)(ge)(ge)(ge)變量的(de)(de)(de)(de)(de)(de)(de)一部分(fen)給(gei)(gei)(gei)無(wu)意識(shi)的(de)(de)(de)(de)(de)(de)(de)修改了(le))。因此其(qi)(qi)他(ta)(ta)(ta)(ta)變量會(hui)“莫名其(qi)(qi)妙(miao)”的(de)(de)(de)(de)(de)(de)(de)出(chu)錯,而(er)且最(zui)可(ke)怕的(de)(de)(de)(de)(de)(de)(de)是(shi)(shi)(shi)(shi)這種出(chu)錯編譯器(qi)(qi)無(wu)法(fa)幫你(ni)發(fa)現,大(da)多(duo)數(shu)時(shi)(shi)(shi)候(hou)隱藏的(de)(de)(de)(de)(de)(de)(de)很深(shen),極難發(fa)現,往往令調試者抓狂、痛不(bu)(bu)(bu)欲生。因此訪(fang)問(wen)(wen)堆(dui)(dui)(dui)內(nei)(nei)存(cun)時(shi)(shi)(shi)應該極為(wei)(wei)小心,一定要檢驗(yan)訪(fang)問(wen)(wen)范(fan)圍(wei),謹防堆(dui)(dui)(dui)訪(fang)問(wen)(wen)越(yue)界(jie)(jie)。
最后一(yi)個(ge)(ge)(ge)示例中(zhong)n等于9999999,這是(shi)我(wo)隨便寫(xie)的(de)一(yi)個(ge)(ge)(ge)很大(da)的(de)數(shu),執行結果為(wei):段(duan)錯誤(Segmentation fault)。熟悉C語言的(de)同(tong)學都知道,一(yi)般(ban)段(duan)錯誤都是(shi)因為(wei)程(cheng)序訪問(wen)了不(bu)該(gai)訪問(wen)的(de)區(qu)域(譬如試圖(tu)寫(xie)代碼(ma)段(duan)),這里也不(bu)例外(wai)。什么原(yuan)因?考慮下上文中(zhong)提到的(de)以(yi)頁為(wei)單位的(de)內(nei)存(cun)管理(li)策略。給你分(fen)配(pei)了一(yi)個(ge)(ge)(ge)頁(一(yi)般(ban)是(shi)4KB),你訪問(wen)時索引(yin)值太大(da)已經超出了這個(ge)(ge)(ge)頁(跑到下個(ge)(ge)(ge)頁甚(shen)至更后面(mian)的(de)頁面(mian)去了),那邊的(de)內(nei)存(cun)頁面(mian)根本不(bu)歸你使(shi)用,你試圖(tu)讀寫(xie)的(de)時候操作系統(tong)的(de)內(nei)存(cun)管理(li)部(bu)分(fen)就(jiu)會一(yi)巴掌把你扇(shan)回(hui)來,給你個(ge)(ge)(ge)Segmentation fault。那個(ge)(ge)(ge)數(shu)字(zi)式我(wo)隨便寫(xie)的(de),你也可以(yi)自己(ji)試試先給個(ge)(ge)(ge)小數(shu)字(zi),然后逐(zhu)漸加大(da),總(zong)會有個(ge)(ge)(ge)臨界點(dian),過了那個(ge)(ge)(ge)點(dian)就(jiu)開始(shi)段(duan)錯誤了。
5.棧溢出
func1到func5這五個示例(li)用來演示棧溢出。
func1是(shi)(shi)典型(xing)的(de)(de)數(shu)組(zu)越界造成(cheng)的(de)(de)棧(zhan)溢出,壓棧(zhan)越界導(dao)致(zhi)沖(chong)毀了(le)(le)函(han)數(shu)調用堆棧(zhan)結(jie)構,致(zhi)使(shi)整(zheng)個程(cheng)序(xu)(xu)崩潰。由此可(ke)見,在C語言(yan)中數(shu)組(zu)訪(fang)問時(shi)一定(ding)要小心檢(jian)查,保證(zheng)不(bu)越界。C語言(yan)為了(le)(le)追(zhui)求最(zui)高的(de)(de)效率(lv),并未(wei)提(ti)供任何數(shu)組(zu)訪(fang)問動態(tai)檢(jian)查(實際(ji)上也沒有提(ti)供編譯(yi)時(shi)數(shu)組(zu)訪(fang)問是(shi)(shi)否越界的(de)(de)靜態(tai)檢(jian)查,其原因(yin)是(shi)(shi)C語言(yan)愿意(yi)相信程(cheng)序(xu)(xu)員,而將檢(jian)查的(de)(de)重任交給了(le)(le)程(cheng)序(xu)(xu)員自己······果然(ran)是(shi)(shi)權(quan)力越大、責任越大啊!),因(yin)此“保衛(wei)世(shi)界和平(ping)的(de)(de)重任就靠你了(le)(le)”。
func2和func3是一對(dui)(dui)對(dui)(dui)比測試。其中(zhong)調(diao)用(yong)(yong)了一個(ge)遞歸函數(shu)(shu)factorial,該函數(shu)(shu)用(yong)(yong)來求(qiu)一個(ge)正整數(shu)(shu)n的(de)(de)階乘。func2中(zhong)n等于(yu)10,計算結果為3628800,是正確(que)的(de)(de)(大(da)家可(ke)以(yi)用(yong)(yong)計算器(qi)自己驗證(zheng))。func3中(zhong)n等于(yu)10000000,運行結果為段(duan)錯誤(其實即(ji)使不段(duan)錯誤,factorial函數(shu)(shu)本身也(ye)無法計算很(hen)大(da)數(shu)(shu)字的(de)(de)階乘,原(yuan)因在(zai)于(yu)函數(shu)(shu)中(zhong)使用(yong)(yong)unsigned int類型(xing)來存(cun)階乘值(zhi)(zhi),這個(ge)類型(xing)的(de)(de)取(qu)值(zhi)(zhi)范圍非常有限,n稍微大(da)一點就會(hui)(hui)溢(yi)出(chu)。但溢(yi)出(chu)只會(hui)(hui)導致計算結果不對(dui)(dui),不會(hui)(hui)造成段(duan)錯誤的(de)(de))。
怎么會(hui)段錯(cuo)誤(wu)呢?因為遞(di)(di)(di)歸(gui)次數(shu)太多(duo),棧(zhan)(zhan)終(zhong)于被撐爆了(le)。遞(di)(di)(di)歸(gui)函(han)數(shu)運行(xing)時,實際上(shang)相當于不(bu)停在(zai)執行(xing)子(zi)函(han)數(shu)調用(yong)(yong),因此(ci)棧(zhan)(zhan)一直(zhi)在(zai)分配而沒(mei)有釋(shi)放。若(ruo)在(zai)棧(zhan)(zhan)使用(yong)(yong)完之前(qian)遞(di)(di)(di)歸(gui)仍(reng)然沒(mei)有結(jie)束返(fan)回(此(ci)時會(hui)逐(zhu)層釋(shi)放棧(zhan)(zhan))就會(hui)發(fa)生段錯(cuo)誤(wu)。這(zhe)是(shi)棧(zhan)(zhan)溢出的另一個(ge)典型情況,請(qing)大家以后使用(yong)(yong)遞(di)(di)(di)歸(gui)算法(fa)解決問題時注(zhu)意這(zhe)個(ge)限制。
func4和(he)func5是一對(dui)對(dui)比測(ce)試。其中(zhong)均(jun)定義了一個局部變(bian)量(liang)數(shu)組a,不(bu)同的(de)是a的(de)大(da)小(xiao)。func4中(zhong)數(shu)組大(da)小(xiao)為1M(注意(yi)a的(de)類型(xing)是int,因此(ci)這里(li)單位是4字節),運行成功(gong)。而func5中(zhong)數(shu)組大(da)小(xiao)為4M,運行時則發(fa)生段錯(cuo)誤。相(xiang)信有了上面上面的(de)講解,大(da)家能夠很容(rong)易想(xiang)明白,局部變(bian)量(liang)分配太多把棧用完(wan)了,所以就段錯(cuo)誤了,就這么簡單。
以(yi)上(shang),通過5個(ge)(ge)示例(li)程(cheng)序為大(da)家演示了棧(zhan)溢出的(de)(de)三種(zhong)情況(kuang)。一般來(lai)說,第一種(zhong)情況(kuang)是明顯(xian)的(de)(de)錯(cuo)誤(wu),且(qie)每次(ci)執行都確定會發生(sheng)錯(cuo)誤(wu)。而(er)后(hou)兩(liang)種(zhong)錯(cuo)誤(wu)則稍微復雜一些,原(yuan)因在于這(zhe)兩(liang)種(zhong)錯(cuo)誤(wu)都依賴于棧(zhan)的(de)(de)大(da)小(xiao)(xiao)。而(er)棧(zhan)的(de)(de)大(da)小(xiao)(xiao)在操作系(xi)統中不是固定的(de)(de),是可以(yi)人為設置(zhi)(zhi)的(de)(de)(譬如linux中使用(yong)(yong)ulimit –s來(lai)查看和設置(zhi)(zhi)用(yong)(yong)戶(hu)進(jin)程(cheng)棧(zhan)大(da)小(xiao)(xiao))。這(zhe)就會帶來(lai)一些很“神奇”的(de)(de)bug,如程(cheng)序在你(ni)的(de)(de)計算機中運行良好,調試通過。結果發給客(ke)戶(hu),10個(ge)(ge)客(ke)戶(hu)中8個(ge)(ge)運行良好,另外(wai)兩(liang)個(ge)(ge)會報錯(cuo)、死(si)機······
這時(shi)(shi)候只要重新設(she)置一個(ge)更(geng)大的用戶棧容(rong)量(liang)(liang)就可(ke)以(yi)解決問題(ti)(ti)。所(suo)以(yi)大家在寫代(dai)碼(ma)時(shi)(shi)一定要注(zhu)意,考慮到你的代(dai)碼(ma)有(you)可(ke)能潛在的問題(ti)(ti)。這樣一旦問題(ti)(ti)暴露即(ji)可(ke)迅速定位,并最快的找到解決方案。不過更(geng)高(gao)級的做法是(shi):在寫代(dai)碼(ma)時(shi)(shi)盡量(liang)(liang)減少可(ke)能存在的問題(ti)(ti),讓(rang)你的程(cheng)序盡量(liang)(liang)更(geng)加健(jian)壯(robust)。
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// function prototype declaration
int heap_overflow(unsigned int n, char c);
void func1(void);
void func2(void);
void func3(void);
void func4(void);
void func5(void);
// 注意:每(mei)個(ge)函數(shu)需(xu)要單獨執行測試(shi),因(yin)此在測試(shi)每(mei)個(ge)函數(shu)時,需(xu)要將其他函數(shu)屏蔽。
int main(void)
{
// 堆溢出訪(fang)問(wen)演(yan)示(shi)
//heap_overflow(9, *t*); // The 9th element = t.
//heap_overflow(99, *g*); // The 99th element = g.
heap_overflow(9999999, *g*); // Segmentation fault
// 棧溢出訪問演示(shi)
//func1(); // stack smashing detected
//func2(); // factorial(10) = 3628800.
//func3(); // Segmentation fault
//func4(); // a[1048576-1] = 5.
//func5(); // Segmentation fault
return 0;
}
int heap_overflow(unsigned int n, char c)
{
char *p = NULL;
p = (char *)malloc(16);
if (NULL == p)
{
printf("fail to get dynamic memory from heap.\n");
return -1;
}
memset(p, 0, 16);
*(p + n) = c;
printf("The %dth element = %c.\n", n, *(p + n));
free(p);
p = NULL;
return 0;
}
void func1(void)
{
char name[8];
strcpy(name, "linus tovards.");
printf("Hello, %s!", name);
}
static unsigned int factorial(unsigned int n)
{
if (n == 1)
return 1;
else
return n * factorial(n - 1);
}
void func2(void)
{
printf("factorial(10) = %d.\n", factorial(10));
}
void func3(void)
{
printf("factorial(10000000) = %d.\n", factorial(10000000));
}
#define M (1 * 1024 * 1024)
#define N (4 * 1024 * 1024)
void func4(void)
{
int a[M];
a[M-1] = 5;
printf("a[%d-1] = %d.\n", M, a[M-1]);
}
void func5(void)
{
int a[N];
a[N-1] = 5;
printf("a[%d-1] = %d.\n", N, a[N-1]);
}
6.堆和棧溢出總結
答:1.函數調(diao)用(yong)層次(ci)太深。函數遞歸(gui)調(diao)用(yong)時,系統要在棧(zhan)(zhan)中不斷保存函數調(diao)用(yong)時的(de)現(xian)場和產生的(de)變量,如果遞歸(gui)調(diao)用(yong)太深,就會造成棧(zhan)(zhan)溢出,這(zhe)時遞歸(gui)無法返回。再有(you),當函數調(diao)用(yong)層次(ci)過深時也可能導(dao)致棧(zhan)(zhan)無法容(rong)納這(zhe)些調(diao)用(yong)的(de)返回地(di)址而造成棧(zhan)(zhan)溢出。
2.動(dong)態(tai)申請空間(jian)使(shi)用之后沒有釋放(fang)。由于C語言中沒有垃(la)圾資源自動(dong)回收(shou)機(ji)制(zhi),因此,需(xu)要(yao)程序主動(dong)釋放(fang)已(yi)經不(bu)再使(shi)用的(de)動(dong)態(tai)地(di)址空間(jian)。申請的(de)動(dong)態(tai)空間(jian)使(shi)用的(de)是堆空間(jian),動(dong)態(tai)空間(jian)使(shi)用不(bu)會造成堆溢出(chu)。
3.數(shu)(shu)(shu)組(zu)訪(fang)問越界。C語言沒有提供數(shu)(shu)(shu)組(zu)下(xia)標越界檢查,如果在(zai)(zai)程序中(zhong)出現數(shu)(shu)(shu)組(zu)下(xia)標訪(fang)問超出數(shu)(shu)(shu)組(zu)范圍,在(zai)(zai)運行過(guo)程中(zhong)可能會(hui)內存(cun)訪(fang)問錯誤。
4.指針(zhen)非(fei)法(fa)訪問(wen)。指針(zhen)保存(cun)了(le)一個非(fei)法(fa)的地(di)址(zhi),通過(guo)這樣(yang)的指針(zhen)訪問(wen)所指向(xiang)的地(di)址(zhi)時會產(chan)生內存(cun)訪問(wen)錯誤。