Called by the compiler when you have more than one page of local variables in your function.
_chkstk Routine is a helper routine for the C compiler. For x86 compilers, _chkstk Routine is called when the local variables exceed 4K bytes; for x64 compilers, it is 8K.
That’s all that you get from _chkstk()’s msdn web page. Nothing more…
Overview
A process starts with a fixed stack space. The top of a stack is pointed to by the ESP register (Extended Stack Pointer) and this is a decrementing pointer. Every function calls results in a stack created for the function inside this Process Stack. Every thread function has its own stack. The stack is a downward growing array. When a function starts, the default stack reservation size is 1 MB.
This is contrasting with the heap’s size, whether theoretically increases to a limit of 4 GB on 32bits OS. See more information here.
Every thread under Windows gets its own block of contiguous memory, and while function calls are made, the stack pointer is increasing and decreasing. In contrast, a different thread within the same process might get a different block of contiguous memory – its own stack. When a context switch occurs, the current thread’s ESP (along with the IP and other registers) are saved in the thread’s context structure, and restored when the thread is activated the next time.
To specify a different default stack reservation size for all threads and fibers, use the STACKSIZE statement in the module definition (.def) file. To change the initially committed stack space, use the dwStackSize parameter of the CreateThread, CreateRemoteThread, or CreateFiber function.
Most stack problems occur in overflows of existing stacks, as their sizes are fixed and they cannot be expanded.
_chkstk() increases the stack when needed by committing some of the pages previously reserved for the stack. If there is no more physical memory available for committed pages, _chkstk fails. When you enter a function (VC++ with the stack checking enabled), it will call the _chkstk located in CHKSTK.ASM. This function does a stack page probing and causes the necessary pages of memory to be allocated using the guard page scheme, if possible. In this function, it is stated that when it encounters _XCPT_GUARD_PAGE_VIOLATION, the OS will attempt to allocate another guarded page and if it encounters _XCPT_UNABLE_TO_GROW_STACK then it’s a stack overflow error. When _XCPT_UNABLE_TO_GROW_STACK is encountered, the stack is not yet set up properly, that is why, that it will not call the catch because calling it will use invalid stack variables which will again cause another exception.
Case – Too many or too big variables on stack
As I said on top, the function stack size is 1 MB. If you miss that and you’re trying to define and use internally an array like this:
int arr[4000][200];
When you’ll compile with VC++ compiler in debug mode, you will have a big surprise: the application is crashing on _chkstk() at the moment the _chkstk() tries to create new memory page on stack and fails.
The output window shows next message:First-chance exception at 0x004116e7 in testApp.exe: 0xC00000FD: Stack overflow.
Unhandled exception at 0x004116e7 in testApp.exe: 0xC00000FD: Stack overflow.
This happens because the 1MB limit is overloaded even on a win32 OS: 4000*200*4 = 3.2MB (approx.).
Same story if you define many local variables and their stack usage overloads the 1MB limit. Off-course the thread stack size can be changed but think once again if it is really needed to do that.
If you really need this big array, then the best solution to avoid this crash is using the heap.
int **arr = new int*[4000]; for(int i=0; i<4000; ++i) { arr[i] = new int[200]; } // and finally don't forget to delete for(int i=0;i<4000; ++i) { delete[] arr[i]; } delete[] arr;
Case – Recursive functions
If you have an infinite recursion, then you will gate the same stack overflow error and the application crashes in _chkstk.asm. Recursive function is not the subject of this article so I don’t go in deep… Here it is a good example of what happens with recursive functions.
The solution is to avoid using recursive functions as much as possible and try to implement an iterative function.
Case – A stack corruption
I have started looking over _chkstk() function at the moment when I got few bugs with crashes with some similarly details. I had to analyze some .dump files and solve a few bugs that contained a call stack with _chkstk() on top.
Most of the .dump files call stack contained a second similarly thing: the call of a thread function (so called ThreadFoo()) that was running in a thread’s pool.
At that moment I started to research why _chkstk() fails and my first track was to debug the stack overflows. I followed a MSDN debugging tutorial and unfortunately I didn’t find something strange. I checked if the local stack variables are not so big in order to fill the ThreadFoo() function’s stack, and it did not.
Then a new study of ThreadFoo() function has followed in order to detect the internal functions calls that can fail in some circumstances. I stopped to some trace function calls and I studied deeply. Those trace functions were defined in an external debug class and each time when a new trace file was added it used an internal buffer (TCHAR szBuff[2048] = _T(“”);).
The writing of this buffer was done using: swprintf(). As we know, this function is unsafe and is not recommended to use. As long as the content of these trace lines was dynamically build (in some cases those line may contain even dynamically build SQL queries that failed) then the length of these trace lines could be higher than 2048 bytes and then guess what: a stack corruption appears! UPS! The stack pointer will be corrupted (the classic stack overflow case).
So I have implemented and used the next macros:
#if defined(UNICODE) || defined(_UNICODE) #define usprintf(x, ...) \ _snwprintf(x, _countof(x)-1, ##__VA_ARGS__); \ x[_countof(x)-1] = 0 #else #define usprintf(x, ...) \ _snprintf(x, _countof(x)-1, ##__VA_ARGS__); \ x[_countof(x)-1] = 0 #endif
Now, if we’re using the safe macro we will have no issues.
int x = 23; TCHAR szMsg[2048] = {0}; usprintf(szMsg, L"Error: %d", x);
A safety alternative way to that buffer was the heap using but the heap access is not fast as the stack access, so I preferred this approach (in a business application every millisecond’s matters for the log system).
After that fixed, I met no other stack corruptions in ThreadFoo() and other code areas.
Even if the top of the call stack was _chkstk() this was not the function that failed. The error appeared because of that stack corruption and _chkstk() has just detected.
Conclusion
If your code produces a stack overflow, then you have to rethink your design in right away:
- If you see _chkstk() on the top of call stack, check if you have no stack corruptions – stack overflow.
- Don’t try to manipulate the stack by yourself. The default 1MB stack size is basically enough
- Allocate dynamically if you’re using big arrays
- If you have recursive functions producing a stack overflow, re-write them using loops (a tip: it is a proven fact that any recursive functions can be programmed non-recursive)
References
Set stack size
Thread Stack Size
_chkstk Routine
Stack (data structure)
Debugging a Stack Overflow – tutorial
Visual C++ apps crashing in _chkstk() under load
Optimization Note (C++) 1: push, pop, call _chkstk
What is Recursion?