STATUS_BAD_INITIAL_STACK (0XC0000009) Fix in Windows Threads
This means your thread creation call gave Windows a bad or null stack pointer. Usually from a corrupted stack address or heap memory that got freed too early.
1. Stack pointer is NULL or points to freed memory
This is the most common reason you're seeing 0XC0000009. When you call CreateThread or NtCreateThread, Windows expects a valid stack address in the lpStackSize or the thread's initial stack pointer. If you pass NULL or a pointer to memory that's already been freed, Windows throws this error right away.
What's happening here: your code might be doing something like this:
// WRONG - stack pointer gets freed before thread starts
void* stack = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);
// some work...
if (something) {
VirtualFree(stack, 0, MEM_RELEASE); // freed!
}
CreateThread(NULL, 0, MyFunc, NULL, 0, NULL); // stack is garbage now
The fix: keep the stack allocation alive until the thread finishes. Use a handle and wait for it explicitly:
// CORRECT
void* stack = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);
HANDLE hThread = CreateThread(NULL, 0, MyFunc, stack, 0, NULL);
WaitForSingleObject(hThread, INFINITE); // wait before freeing
VirtualFree(stack, 0, MEM_RELEASE);
CloseHandle(hThread);
I've seen this on Windows 10 22H2 and Windows 11 23H2 when people use custom stack allocation for thread pools. The debugger will show RtlpStartThread failing and the call stack points to NtCreateThread. Check your VirtualAlloc calls — are they actually committing memory? If you use MEM_RESERVE without MEM_COMMIT, the stack region exists but isn't backed by physical RAM, and Windows will still reject it.
2. Stack pointer is not aligned to 16 bytes
Windows x64 requires the stack pointer to be 16-byte aligned when starting a thread. If you provide a custom stack address that's not aligned, NtCreateThread returns STATUS_BAD_INITIAL_STACK. This is a CPU requirement: SSE instructions like movaps fault on unaligned memory.
The fix is dead simple: mask the bottom 4 bits of your stack address:
void* stack = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);
uintptr_t aligned = ((uintptr_t)stack + 15) & ~15;
// Pass aligned address as the stack start
But wait — there's a nuance. The stack grows downward. So you need to point to the end of the aligned block, not the start. Here's what actually works:
void* base = VirtualAlloc(NULL, 4096 + 16, MEM_COMMIT, PAGE_READWRITE);
uintptr_t end = (uintptr_t)base + 4096;
uintptr_t aligned_end = end & ~15; // align the top
// Pass aligned_end as the initial stack pointer
If you skip the alignment padding, you might hit a different error later (like STATUS_DATATYPE_MISALIGNMENT), but the 0XC0000009 error shows up first. I'd argue this is the second most common cause because people copy-paste old x86 code that didn't need alignment.
3. Heap corruption freed the stack under you
This one's sneaky. Your stack allocation looks fine, alignment is correct, but NtCreateThread still fails with 0XC0000009. What's happening: a buffer overflow somewhere in your code corrupted the heap metadata. When VirtualAlloc tries to commit the stack pages, the memory manager detects the corruption and fails.
You'll see this in debug builds when you enable Application Verifier or GFlags. The stack trace will show RtlpCommitHeapPages failing before the thread even starts. I've nailed this down to two suspects:
- Writing past the end of a heap-allocated buffer that lives adjacent to the stack memory
- Double-freeing a heap block that shares the same address space as your stack allocation
To diagnose this, run:
gflags /p /enable YourApp.exe /full
Then reproduce the crash. The debugger will break at the exact heap corruption point, not the thread creation. Fix the overflow or double-free. Once the heap is clean, 0XC0000009 vanishes.
One real-world scenario: I saw this in a game engine that used a custom memory allocator. The allocator returned a stack block, but a nearby string buffer overflowed by 4 bytes. That tiny overflow corrupted the heap's free list. The fix: use _CrtSetDbgFlag with _CRTDBG_CHECK_CRT_DF during development to catch it early.
Quick reference table
| Cause | Check | Fix |
|---|---|---|
| Null or freed stack pointer | VirtualAlloc success, no VirtualFree before thread start | Wait on thread handle before freeing |
| Misaligned stack address | Stack pointer mod 16 == 0? | Align to 16 bytes, use top of block |
| Heap corruption | Run with GFlags, check RtlpCommitHeapPages | Fix buffer overflow or double-free |
Was this solution helpful?