每当创建一个线程的时候,系统会为线程的堆栈保留一个栈区的空间区域,并将一些物理存储器提交给这个已保留的区域,我查看了VS2015,该默认设置大小是1MB。
它可以自己设置,在 项目-->属性-->链接器-->系统--->堆栈保留大小 这个地方填写自己希望的栈大小。
// 堆保留大小 1M 堆提交大小 4KB
// 栈保留大小 1M 栈提交大小 4KB
在页面大小是4KB的计算机上创建一个堆栈区域,其物理存储均具有页面保护属性,即PAGE_READWRITE。
线程访问时,从栈顶到栈底,页面状态不断变为已提交的页面,但是最底下的页面总是被保留的,从来不会被提交,这样做的目的是为了防止不小心改写进程所需要的其他数据。因为如果栈底下方的地址上,另一个地址空间区域已经提交了物理存储器,那么就有可能改写了其他数据,这是非常危险又隐蔽的错误。
结合SEH异常处理机制,可以写一个简单的求和函数,用递归的方法,让它发生栈溢出,并对异常进行捕获。
#include "stdafx.h"
#include <windows.h>
// 堆保留大小 1M 堆提交大小 4KB
// 栈保留大小 1M 栈提交大小 4KB
UINT Sum(UINT uNum);
LONG WINAPI FilterFunc(DWORD dwExceptionCode);
DWORD WINAPI SumThreadFunc(PVOID Param);
int main()
{
DWORD ThreadId;
UINT uSum = 5000; //大概可以计算4700以内的数
HANDLE ThreadHandle = CreateThread(NULL, 0,
SumThreadFunc, (PVOID)(UINT_PTR)uSum, 0, &ThreadId);
WaitForSingleObject(ThreadHandle, INFINITE);
GetExitCodeThread(ThreadHandle, (PDWORD)&uSum);
CloseHandle(ThreadHandle);
if (uSum == UINT_MAX)
{
printf("The number is too big, please enter a smaller number
");
}
else
{
printf("%d
", uSum);
}
return 0;
}
UINT Sum(UINT uNum)
{
return((uNum == 0) ? 0 : (uNum + Sum(uNum - 1)));
}
LONG WINAPI FilterFunc(DWORD dwExceptionCode)
{
return((dwExceptionCode == STATUS_STACK_OVERFLOW)
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);
}
DWORD WINAPI SumThreadFunc(PVOID Param)
{
UINT uSumNum = PtrToUlong(Param); //指针转ULONG
UINT uSum = UINT_MAX; // 错误数字
__try
{
uSum = Sum(uSumNum); //递归求和
}
__except (FilterFunc(GetExceptionCode())) //如果是EXCEPTION_EXECUTE_HANDLER (1)
{
// 说明堆栈溢出
// 添加一些自己的处理
printf("堆栈溢出了
");
}
return uSum;
}
创建独立线程的理由:
1.每个线程保证拥有自己的1MB堆栈空间,而不是和他人分享1MB
2.当发生溢出时,每个线程只得到一次通知
3.系统自动收回提交给堆栈的物理存储器。