On windows, the stack (for specific thread) grows on-demand until the stack size specified for this thread prior to its creation has been reached.
On-demand growing is impelmented using guard pages, in that there's a only a fragment of stack available initially, followed by a guard page, which, when hit, will trigger an exception - this exception is special, and is handled by the system for you - the handling increases the available stack space (also checked if a limit has been reached!) and the read operation is retried.
Once the limit is reached, there's no more growing which results in stack overflow exception.
The current stack base and limit are stored in thread environment block, in a struct called _NT_TIB
(thread information block).
If you have a debugger handy, this is what you see:
0:000> dt ntdll!_teb @$teb nttib.
+0x000 NtTib :
+0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x00130000
+0x008 StackLimit : 0x0011e000
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x7ffdf000 _NT_TIB
The StackLimit attribute will get updated on-demand.
If you check the attributes on this memory block, you'll see something similar to that:
0:000> !address 0x0011e000
00030000 : 0011e000 - 00012000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageStack
Pid.Tid abc.560
And checking a page next to it reveals the guard attribute:
0:000> !address 0x0011e000-1000
00030000 : 0011d000 - 00001000
Type 00020000 MEM_PRIVATE
Protect 00000104 PAGE_READWRITE | PAGE_GUARD
State 00001000 MEM_COMMIT
Usage RegionUsageStack
Pid.Tid abc.560
Hope it helps.