全面介绍Windows内存管理机制及C++内存分配实例(六):堆栈
本文背景:
在編程中,很多Windows或C++的內存函數不知道有什么區別,更別談有效使用;根本的原因是,沒有清楚的理解操作系統的內存管理機制,本文企圖通過簡單的總結描述,結合實例來闡明這個機制。
本文目的:
對Windows內存管理機制了解清楚,有效的利用C++內存函數管理和使用內存。
本文內容:
本文一共有六節,由于篇幅較多,故按節發表。其他章節請看本人博客的Windows內存管理及C++內存分配實例(一)(二)(三)(四)和(五)。
?
?
6.??????內存管理機制--堆棧?(Stack)
·????????使用場合
操作系統為每個線程都建立一個默認堆棧,大小為1M。這個堆棧是供函數調用時使用,線程內函數里的各種靜態變量都是從這個默認堆棧里分配的。
·????????堆棧結構
默認1M的線程堆棧空間的結構舉例如下,其中,基地址為0x0004 0000,剛開始時,CPU的堆棧指針寄存器保存的是棧頂的第一個頁面地址0x0013 F000。第二頁面為保護頁面。這兩頁是已經分配物理存儲器的可用頁面。
隨著函數的調用,系統將需要更多的頁面,假設需要另外5頁,則給這5頁提交內存,刪除原來頁面的保護頁面屬性,最后一頁賦予保護頁面屬性。
當分配倒數第二頁0x0004 1000時,系統不再將保護屬性賦予它,相反,它會產生堆棧溢出異常STATUS_STACK_OVERFLOW,如果程序沒有處理它,則線程將退出。最后一頁始終處于保留狀態,也就是說可用堆棧數是沒有1M的,之所以不用,是防止線程破壞棧底下面的內存(通過違規訪問異常達到目的)。
?
?
當程序的函數里分配了臨時變量時,編譯器把堆棧指針遞減相應的頁數目,堆棧指針始終都是一個頁面的整數倍。所以,當編譯器發現堆棧指針位于保護頁面之下時,會插入堆棧檢查函數,改變堆棧指針及保護頁面。這樣,當程序運行時,就會分配物理內存,而不會出現訪問違規。
·????????使用例子
改變堆棧默認大小:
有兩個方法,一是在CreateThread()時傳一個參數進去改變;
二是通過鏈接命令:
#pragma comment(linker,"/STACK:102400000,1024000")
第一個值是堆棧的保留空間,第二個值是堆棧開始時提交的物理內存大小。本文將堆棧改變為100M。
?????????堆棧溢出處理:
????????如果出現堆棧異常不處理,則導致線程終止;如果你只做了一般處理,內?存
????????結構已經處于破壞狀態,因為已經沒有保護頁面,系統沒有辦法再拋出堆棧溢
????????出異常,這樣的話,當再次出現溢出時,會出現訪問違規操作
????????STATUS_ACCESS_VIOLATION,這是線程將被系統終止。解決辦法是,恢復
???????堆棧的保護頁面。請看以下例子:
???????C++程序如下:
bool?handle=true;
????????????static?MEMORY_BASIC_INFORMATION mi;
????????????LPBYTE lpPage;
????????????//得到堆棧指針寄存器里的值
????????????_asm?mov lpPage, esp;
????????????//?得到當前堆棧的一些信息
????????????VirtualQuery(lpPage, &mi,?sizeof(mi));
????????????//輸出堆棧指針
????????????printf("堆棧指針=%x/n",lpPage);
????????????//?這里是堆棧的提交大小
????????????printf("已用堆棧大小=%d/n",mi.RegionSize);
????????????printf("堆棧基址=%x/n",mi.AllocationBase);
???????????????????????????????????
????????????for(int?i=0;i<2;i++)
????????????{
????????????????????????__try
????????????????????????{
????????????????????????????????????__try
????????????????????????????????????{
????????????????????????????????????????????????__try
????????????????????????????????????????????????{
????????????????????????????????????????????????????????????cout<<"**************************"<<endl;
????????????????????????//如果是這樣靜態分配導致的堆棧異常,系統默認不拋出異常,捕獲不到
????????????????????????????????????????????????????????????//char a[1024*1024];
????????????????????????????????????????????????//動態分配棧空間,有系統調用Alloca實現,自動釋放
????????????????????????????????????????????????????????????Add(1000);
????????????????????????????????????????????????????????????//系統可以捕獲違規訪問
????????????????????????????????????????????????????????????int?* p=(int*)0xC00000000;
????????????????????????????????????????????????????????????*p=3;
????????????????????????????????????????????????????????????cout<<"執行結束"<<endl;
????????????????????????????????????????????????}
????????????????????????????????????????????????__except(GetExceptionCode()==STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
????????????????????????????????????????????????{
????????????????????????????????????????????????????????????cout<<"Excpetion 1"<<endl;
????????????????????????????????????????????????}
????????????????????????????????????}
????????????????????????????????????__except(GetExceptionCode()==STATUS_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
????????????????????????????????????{
????????????????????????????????????????????????cout<<"Exception 2"<<endl;
????????????????????????????????????????????????if(handle)
????????????????????????????????????????????????{
????????????????????????????????????????????????//做堆棧破壞狀態恢復
????????????????????????????????????????????????????????????LPBYTE lpPage;
????????????????????????????????????????????????????????????static?SYSTEM_INFO si;
????????????????????????????????????????????????????????????static?MEMORY_BASIC_INFORMATION mi;
????????????????????????????????????????????????????????????static?DWORD dwOldProtect;
?
????????????????????????????????????????????????????????????//?得到內存屬性
????????????????????????????????????????????????????????????GetSystemInfo(&si);
?
????????????????????????????????????????????????????????????//?得到堆棧指針
????????????????????????????????????????????????????????????_asm?mov lpPage, esp;
????????????????????????????????????????????????????????????//?查詢堆棧信息
????????????????????????????????????????????????????????????VirtualQuery(lpPage, &mi,?sizeof(mi));
????????????????????????????????????????????????????????????printf("壞堆棧指針=%x/n",lpPage);
????????????????????????????????????????????????????????????//?得到堆棧指針對應的下一頁基址
lpPage = (LPBYTE)(mi.BaseAddress)-si.dwPageSize;
????????????????????????????????????????????????????????????printf("已用堆棧大小=%d/n",mi.RegionSize);
????????????????????????????????????????????????????????????printf("壞堆棧基址=%x/n",mi.AllocationBase);
????????????????????????????????????????????????????????????//釋放準保護頁面的下面所有內存
????????????????????????????????????????????????????????????if?(!VirtualFree(mi.AllocationBase,
(LPBYTE)lpPage - (LPBYTE)mi.AllocationBase,
????????????????????????????????????????????????????????????????????????MEM_DECOMMIT))
????????????????????????????????????????????????????????????{?????????
????????????????????????????????????????????????????????????????????????exit(1);
????????????????????????????????????????????????????????????}
????????????????????????????????????????????????????????????//?改頁面為保護頁面
????????????????????????????????????????????????????????????if?(!VirtualProtect(lpPage, si.dwPageSize,
????????????????????????????????????????????????????????????????????????PAGE_GUARD | PAGE_READWRITE,
????????????????????????????????????????????????????????????????????????&dwOldProtect))
????????????????????????????????????????????????????????????{
????????????????????????????????????????????????????????????????????????exit(1);
????????????????????????????????????????????????????????????}
????????????????????????????????????????????????}
????????????????????????????????????????????????printf("Exception handler %lX/n", _exception_code());
????????????????????????????????????}
????????????????????????}
????????????????????????__except(EXCEPTION_EXECUTE_HANDLER)
????????????????????????{
????????????????????????????????????cout<<"Default handler"<<endl;
????????????????????????}
????????????}
???????????
????????????cout<<"正常執行"<<endl;
????????????//分配空間,耗用堆棧
????????????char?c[1024*800];
????????????printf("c[0]=%x/n",c);
????????????printf("c[1024*800]=%x/n",&c[1024*800-1]);
}
?
void?ThreadStack::Add(unsigned?long?a)
{
????????????//深遞歸,耗堆棧
????????????char?b[1000];
????????????if(a==0)
????????????return;
????????????Add(a-1);
?
}
?
程序運行結果如下:
?
可以看見,在執行遞歸前,堆棧已被用了800多K,這些是在編譯時就靜態決定了。它們不再占用進程空間,因為堆棧占用了默認的1M進程空間。分配是從棧頂到棧底的順序。
當第一次遞歸調用后,系統捕獲到了它的溢出異常,然后堆棧指針自動恢復到原來的指針值,并且在異常處理里,更改了保護頁面,確保第二次遞歸調用時不會出現訪問違規而退出線程,但是,它仍然會導致堆棧溢出,需要動態的增加堆棧大小,本文沒有對這個進行研究,但是試圖通過分配另外內存區,改變堆棧指針,但是沒有奏效。
注意:在一個線程里,全局變量加上任何一個函數里的臨時變量,如果超過堆棧大小,當調用這個函數時,都會出現堆棧溢出,這種溢出系統不會拋出堆棧溢出異常,而直接導致線程退出。
對于函數1調用函數2,而函數n-1又調用函數n的嵌套調用,每層調用不算臨時變量將損失240字節,所以默認線程最多有1024*(1024-2)/240=4360次調用。加上函數本身有變量,這個數目會大大減少。
?
至此,內存管理機制完全介紹完畢! 謝謝光顧!
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的全面介绍Windows内存管理机制及C++内存分配实例(六):堆栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开始简单项目的第二周
- 下一篇: pthread_cleanup_push