IT培训机构|91免费精品视频|专注编程培训|91免费精品|软件开发培训_91免费国产视频_华清远见教育

當前位置:首頁 > 嵌入式培訓 > 嵌入式學習 > 講師博文 > 堆棧溢出一般是由什么原因導致的?

堆棧溢出一般是由什么原因導致的? 時間:2018-12-24      來源:華清遠見

堆棧溢出一般都是由堆棧越界訪問導致的。例如函數內局部變量數組越界訪問,或者函數內局部變量使用過多,超出了操作系統為該進程分配的棧的大小也會導致堆棧溢出。深度解析:

首先要區分清楚堆、棧、堆棧這幾個名詞。堆(heap)和棧(stack)是兩種不同的內存管理機制:

1.堆

堆被稱為動態內存,由堆管理器(系統里的大人物,山高皇帝遠不用去管它)管理,程序中可以使用malloc函數來(向堆管理器)申請分配堆內存,使用完后使用free函數釋放(給堆管理器回收)。堆內存的特點是:在程序運行過程中才申請分配,在程序運行中即釋放(因此稱為動態內存分配技術)。

2.棧

棧是C語言使用的一種內存自動分配技術(注意是自動,不是動態,這是兩個概念),自動指的是棧內存操作不用C程序員干預,而是自動分配自動回收的。C語言中局部變量就分配在棧上,進入函數時局部變量需要的內存自動分配,函數結束退出時局部變量對應的內存自動釋放,整個過程中程序員不需要人為干預。

堆棧這個詞純粹是用來坑人的。堆就是堆(heap),棧就是棧(stack),根本沒有另外一種內存管理機制叫堆棧。大多數時候有人說起堆棧,其實他想說的是棧,以前早些的時候,這方面的命名并不是特別準確。(別人說堆棧的時候,大家知道他其實想說的是棧就行了,自己就不要再用這個不準確的詞了)。既然堆和棧都是用來管理內存的機制,使用時就有一定的規則。無視規則的錯誤使用(C語言設計時賦予了程序員很大的自由度,所以有些錯誤語言本身是不會檢查的,全憑程序員自己把握。)就可以導致一些內存錯誤,如內存泄漏、溢出錯誤等。

3.存泄漏

內存泄漏主要發生在堆內存使用中。譬如我們使用malloc申請了內存,使用過后并未釋放而丟棄了指向該內存的指針(這個指針是這段內存的唯一記錄,程序中釋放該段內存都靠這個指針了),那么這段堆內存就泄漏掉了(堆管理器以為程序還在使用,所以不會將這段內存再次分配給別的程序)。必須等到這個程序徹底退出后,系統回收該程序所使用的所有資源(申請的內存,使用的文件描述符等)時這些泄漏的內存才重新回到堆管理器的懷抱。

內存溢出在堆和棧中都有可能發生。參見章節示例1_2_stack_overflow.c中的8個示例函數,其中前三個函數與堆溢出有關,后五個函數與棧溢出有關。

4.堆溢出

函數heap_overflow中使用malloc申請了16字節動態內存,然后嘗試去讀寫這16個內存之中的第n個。三個測試分別給n賦值9,99和9999999,得到的結果很有意思(見程序后面的注釋,大家也可以自己編譯運行測試),現在我們來探討其中的原理。

n等于9的時候沒什么好說的,本該正確運行,這個相信大家沒有異議。n等于99的時候······竟然也可以正確運行,這個相信很多人就有點想不通了。我們申請的空間只有16字節啊,怎么竟然還可以訪問第99個字節空間呢(這就是所謂的堆溢出訪問)?這時候實際已經堆溢出了,但是為什么結果沒有出錯呢?原因在操作系統的內存分配策略中。譬如linux中內存是按照頁(Page,一般是4K字節一個頁)來管理的,操作系統給進程分配內存本質上都是以頁為單位進行的。也就是說你雖然只要求了16個字節,但是實際分配給你這個進程的可能是一個頁(4K字節)。這個頁中只有這16個字節是你自己的“合法財產”,其他部分你不該去訪問(一訪問就堆越界)。但是因為操作系統對內存的訪問權限管理是以頁為單位的,因此本頁內16字節之外的內存你(非法)訪問時系統仍然不會報錯,并且確實能夠達成目的(示例中n等于99時讀寫仍然正確)。那是不是說堆越界是無害的,完全不用擔心呢?顯然不是。因為堆越界最大的傷害不是對自己,而是對“別人”。因為除了你申請的16字節外本頁面內其他內存可能會被堆管理器分配給其他變量,你越界訪問時意味著你可能踐踏了其他變量的有效區域(譬如我們給第99個字節賦值為g時,很可能把別處動態分配的一個變量的一部分給無意識的修改了)。因此其他變量會“莫名其妙”的出錯,而且最可怕的是這種出錯編譯器無法幫你發現,大多數時候隱藏的很深,極難發現,往往令調試者抓狂、痛不欲生。因此訪問堆內存時應該極為小心,一定要檢驗訪問范圍,謹防堆訪問越界。

最后一個示例中n等于9999999,這是我隨便寫的一個很大的數,執行結果為:段錯誤(Segmentation fault)。熟悉C語言的同學都知道,一般段錯誤都是因為程序訪問了不該訪問的區域(譬如試圖寫代碼段),這里也不例外。什么原因?考慮下上文中提到的以頁為單位的內存管理策略。給你分配了一個頁(一般是4KB),你訪問時索引值太大已經超出了這個頁(跑到下個頁甚至更后面的頁面去了),那邊的內存頁面根本不歸你使用,你試圖讀寫的時候操作系統的內存管理部分就會一巴掌把你扇回來,給你個Segmentation fault。那個數字式我隨便寫的,你也可以自己試試先給個小數字,然后逐漸加大,總會有個臨界點,過了那個點就開始段錯誤了。

5.棧溢出

func1到func5這五個示例用來演示棧溢出。

func1是典型的數組越界造成的棧溢出,壓棧越界導致沖毀了函數調用堆棧結構,致使整個程序崩潰。由此可見,在C語言中數組訪問時一定要小心檢查,保證不越界。C語言為了追求最高的效率,并未提供任何數組訪問動態檢查(實際上也沒有提供編譯時數組訪問是否越界的靜態檢查,其原因是C語言愿意相信程序員,而將檢查的重任交給了程序員自己······果然是權力越大、責任越大啊!),因此“保衛世界和平的重任就靠你了”。

func2和func3是一對對比測試。其中調用了一個遞歸函數factorial,該函數用來求一個正整數n的階乘。func2中n等于10,計算結果為3628800,是正確的(大家可以用計算器自己驗證)。func3中n等于10000000,運行結果為段錯誤(其實即使不段錯誤,factorial函數本身也無法計算很大數字的階乘,原因在于函數中使用unsigned int類型來存階乘值,這個類型的取值范圍非常有限,n稍微大一點就會溢出。但溢出只會導致計算結果不對,不會造成段錯誤的)。

怎么會段錯誤呢?因為遞歸次數太多,棧終于被撐爆了。遞歸函數運行時,實際上相當于不停在執行子函數調用,因此棧一直在分配而沒有釋放。若在棧使用完之前遞歸仍然沒有結束返回(此時會逐層釋放棧)就會發生段錯誤。這是棧溢出的另一個典型情況,請大家以后使用遞歸算法解決問題時注意這個限制。

func4和func5是一對對比測試。其中均定義了一個局部變量數組a,不同的是a的大小。func4中數組大小為1M(注意a的類型是int,因此這里單位是4字節),運行成功。而func5中數組大小為4M,運行時則發生段錯誤。相信有了上面上面的講解,大家能夠很容易想明白,局部變量分配太多把棧用完了,所以就段錯誤了,就這么簡單。

以上,通過5個示例程序為大家演示了棧溢出的三種情況。一般來說,第一種情況是明顯的錯誤,且每次執行都確定會發生錯誤。而后兩種錯誤則稍微復雜一些,原因在于這兩種錯誤都依賴于棧的大小。而棧的大小在操作系統中不是固定的,是可以人為設置的(譬如linux中使用ulimit –s來查看和設置用戶進程棧大小)。這就會帶來一些很“神奇”的bug,如程序在你的計算機中運行良好,調試通過。結果發給客戶,10個客戶中8個運行良好,另外兩個會報錯、死機······

這時候只要重新設置一個更大的用戶棧容量就可以解決問題。所以大家在寫代碼時一定要注意,考慮到你的代碼有可能潛在的問題。這樣一旦問題暴露即可迅速定位,并最快的找到解決方案。不過更高級的做法是:在寫代碼時盡量減少可能存在的問題,讓你的程序盡量更加健壯(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);

// 注意:每個函數需要單獨執行測試,因此在測試每個函數時,需要將其他函數屏蔽。

int main(void) 

// 堆溢出訪問演示

//heap_overflow(9, *t*); // The 9th element = t.

//heap_overflow(99, *g*); // The 99th element = g.

heap_overflow(9999999, *g*); // Segmentation fault

// 棧溢出訪問演示 

//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.函數調用層次太深。函數遞歸調用時,系統要在棧中不斷保存函數調用時的現場和產生的變量,如果遞歸調用太深,就會造成棧溢出,這時遞歸無法返回。再有,當函數調用層次過深時也可能導致棧無法容納這些調用的返回地址而造成棧溢出。 

2.動態申請空間使用之后沒有釋放。由于C語言中沒有垃圾資源自動回收機制,因此,需要程序主動釋放已經不再使用的動態地址空間。申請的動態空間使用的是堆空間,動態空間使用不會造成堆溢出。 

3.數組訪問越界。C語言沒有提供數組下標越界檢查,如果在程序中出現數組下標訪問超出數組范圍,在運行過程中可能會內存訪問錯誤。 

4.指針非法訪問。指針保存了一個非法的地址,通過這樣的指針訪問所指向的地址時會產生內存訪問錯誤。

上一篇:GPIO是什么?

下一篇:嵌入式:并行接口介紹

熱點文章推薦
華清學員就業榜單
高薪學員經驗分享
熱點新聞推薦
前臺專線:010-82525158 企業培訓洽談專線:010-82525379 院校合作洽談專線:010-82525379 Copyright © 2004-2022 北京華清遠見科技集團有限公司 版權所有 ,京ICP備16055225號-5京公海網安備11010802025203號

回到頂部

主站蜘蛛池模板: 浙江中瓷阀门有限公司| 乌鲁木齐万疆通管道设备有限公司 销售热线;13565955557-新疆 乌鲁木齐 万疆通 管道设备 波纹补偿器 膨胀节 金属软管 伸缩器 管件 阀门 维修 | 橡胶粉碎机_轮胎粉碎机_橡胶切条机_橡胶粉碎机价格_河南鑫世昌机械制造有限公司 | 上海品牌设计公司|品牌策划公司|包装设计公司|上海全案LOGO设计VI设计-木马品牌设计 | 箱式污泥采样器-全自动旋转振荡器-恒温石墨电热板-常州亿通分析仪器制造有限公司 | 投影仪配件,苏州投影仪维修,B60数显表维修-苏州市加野仪器有限公司 | 全铝家居_十大全铝家具品牌_全铝衣柜橱柜——佛山欧格美铝业 | 实验室实验台-钢木实验台-实验室通风柜-实验室家具-苏州奥纳威 | 环保除尘设备_燃气/燃油热水锅炉_光氧空气净化器_蒸汽玉米压片机_压片设备_烘干设备-山东金盾节能环保设备有限公司 | 客服外包_电话调查_电话调研_售前售后在线外包客服公司-北京美宸互联 | 人工气候-智能-低温生化培养箱厂家|价格-上海予卓仪器 | 郑州空调维修_郑州中央空调维修_空调清洗维保-郑州大晟机电设备安装工程有限公司 | 昆明集装箱-云南住人集装箱活动房厂家|移动板房出租赁定制 | 上海祝融起重机械有限公司-德国耶鲁手拉葫芦|耶鲁手拉葫芦|耶鲁手扳葫芦|耶鲁电动葫芦经销代理 | 南尔智能科技 南尔 小南管家 智慧中控屏 智能语音面板 - 深圳市南尔智能科技有限公司 | 景德镇古窑民俗博览区-国家AAAAA级旅游景区_全国旅游标准化示范景区_国家文化产业示范基地_国家级非物质文化遗产生产性保护示范基地--官方网站 | 众学稳尚升学规划网-高考填报志愿机构_新高考选科指导 | 上海办公室租赁-写字楼出租、创意产业园区厂房招商、孵化器众创联合办公空间出租网 | 泊头市鸿海泵业有限公司--导热油泵,高温油泵,沥青保温泵,圆弧泵,齿轮油泵,高粘度泵,自吸离心油泵,罗茨油泵为主的专业生产厂家 | 宣城安安橡塑有限责任公司| 耐压测试仪(检测电气设备绝缘性能)百科 | 热泵烘干机_食品烘干机_水果烘干机_蔬菜烘干机_河南蓝天机械制造有限公司 | 九江赛璐珞实业有限公司-赛璐珞板,赛璐璐板材,PVC装饰膜,PVC片材,醋酸纤维胶板_多彩装饰材料生产厂家 | 无锡大型数控龙门铣,喷涂加工,回火抛丸加工,精密不锈钢焊接机床身机床底座制造加工-无锡美高帝机械有限公司 | 随州网站建设_随州建站公司_随州SEO优化排名_随州网络公司-随州市金思维网络科技有限公司 | 三叶罗茨鼓风机_三叶罗茨风机厂家_山东九洲四海机械有限公司 | 山西亿企邦财税服务有限公司| 聊城钢管厂,无缝钢管厂家-山东旺耀金属制品有限公司 | 仪器仪表维修_示波器维修_进口分析仪维修_热像仪维修_上海仰光电子仪器仪表维修部 | 网站建设_定制网站_高端网页设计开发_建站公司_深圳湉晨网络 | 济宁三石工程机械有限公司-首页-小型起重机、环卫设备、小松配件 | 性激素6项实验检测,放射免疫实验检测,明胶酶谱试剂盒,科研用人血清,质控标品,试剂盒-南京信帆生物技术有限公司 | 上海海外置业展_2024海外置业移民留学展_4月5-7日_企业参展处/免费领门票 | 恒温干燥箱厂家-烘箱厂家-马弗炉厂家-生化培养箱-上海有丰科学仪器有限公司 | 重庆自考网-重庆自学考试| 有机肥设备|有机肥生产线|有机肥料生产设备|河南通达重工科技有限公司 | 郑州腾飞建设工程集团有限公司 | 湖南流水线-湖南自动化设备-湖南输送设备-湘潭市友工自动化设备有限公司 | 芜湖藦卡机器人科技有限公司 | 组合包装箱,折叠包装箱,烟台木箱,烟台包装箱-烟台顺达包装有限责任公司 | 一体化净水器设备-浸没式膜水处理设备-智慧水务-超滤膜-模块化净水设备-浙江华晨环保有限公司 |