Windows does not commit the stack memory immediately; instead, it reserves the address space for it, and commits it page-by-page when it is accessed. Read this page for more info.
As a result, stack address space consists of three contiguous regions:
- Reserved but uncommitted memory which can be used for stack growth (but was never accessed yet);
- Guard page, which was never accessed yet too, and serves to trigger stack growth when accessed;
- Committed memory, i.e. stack memory which was ever accessed by the thread.
This allows us to construct a function that obtains stack size (with page size granularity):
static size_t GetStackUsage()
{
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(&mbi, &mbi, sizeof(mbi));
// now mbi.AllocationBase = reserved stack memory base address
VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi));
// now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack
// skip it
VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
// now (mbi.BaseAddress, mbi.RegionSize) describe the guard page
// skip it
VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
// now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack
return mbi.RegionSize;
}
One thing to consider: CreateThread
allows to specify initial stack commit size (via dwStackSize
parameter, when STACK_SIZE_PARAM_IS_A_RESERVATION
flag is not set). If this parameter is nonzero, our function will return correct value only when stack usage becomes greater than dwStackSize
value.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…