A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
The behavior comes from how MSVC implements thread_local and thread-safe initialization of local/static objects, and from OS-level limits and bugs around thread-local storage (TLS) and heap usage. The key points from the provided documentation:
-
thread_localobjects and TLS implementation
- On Windows,
thread_localis functionally equivalent to__declspec(thread)for native C++ code. -
thread_localcan only be applied to objects with static storage duration (global, static local, static data members). Any block-scopethread_localis implicitlystatic. - Each thread gets its own instance, created when the thread starts and destroyed when it exits.
Reference behavior:
"A variable declared with the
thread_localspecifier is accessible only on the thread on which it's created. The variable is created when the thread is created, and it's destroyed when the thread is destroyed. Each thread has its own copy of the variable. On Windows,thread_localis functionally equivalent to the Microsoft-specific__declspec( thread )attribute."
This means the runtime and OS must set up TLS slots and run constructors early in thread startup, which can interact with heap allocation and OS TLS limits.
- Thread-safe initialization and TLS usage
MSVC’s implementation of thread-safe initialization for static locals uses TLS internally:
"Thread-safe static local variables use thread-local storage (TLS) internally to provide efficient execution when the static has already been initialized. The implementation of this feature relies on Windows operating system support functions in Windows Vista and later operating systems."
Even though the example uses a global static thread_local object, the same TLS machinery is involved. The initialization code runs very early and uses TLS and the heap. If there is an OS bug or a TLS limit/interaction in a specific Windows build, heap allocation during TLS initialization can trigger an access violation or similar crash.
- OS-level TLS limits and crashes
The documentation explicitly notes that exceeding TLS-related limits or issues in older OSes can cause crashes:
"These operating systems also have a lower limit on the number of TLS sections that can be loaded. Exceeding the TLS section limit can cause a crash. If this is a problem in your code … use
/Zc:threadSafeInit-to disable the thread-safe initialization code."
While that remark is about older Windows versions, it shows that TLS initialization and heap use are sensitive to OS behavior and limits. A regression or change in specific Windows 11 builds can therefore manifest as crashes when a thread_local object’s constructor allocates from the heap.
- Managed/WinRT restriction (for completeness)
In managed (/clr) or WinRT (/ZW) code, dynamic initialization of thread_local or __declspec(thread) is not allowed:
"In managed or WinRT code, variables declared by using the
__declspec(thread)storage class modifier attribute or thethread_localstorage class specifier cannot be initialized with an expression that requires evaluation at run-time. A static expression is required to initialize__declspec(thread)orthread_localdata in these runtime environments."
This is enforced by compiler error C2482. In pure native code (as in the example), this restriction does not apply, but it illustrates that dynamic initialization of TLS objects is a known fragile area.
- Practical mitigations based on the documented behavior
Given the above, and staying within the documented behavior:
- Avoid heavy or heap-allocating constructors for
thread_localglobals that run at process or thread startup. Prefer:- A trivially constructible
thread_localobject plus an explicitinit()that does heap work later, or - A
thread_localpointer initialized tonullptrand lazily allocating on first use.
- A trivially constructible
- If the crash is related to thread-safe initialization overhead and TLS usage, and thread-safety is not required in the specific scenario, the documentation suggests disabling thread-safe initialization:
- Compile with
/Zc:threadSafeInit-to revert to non-thread-safe static initialization and avoid the UCRT-based TLS helper code:"To avoid taking a dependency on the UCRT, or to preserve the non-thread-safe initialization behavior of versions of Visual Studio prior to Visual Studio 2015, use the
/Zc:threadSafeInit-option. If you know that thread-safety is not required, use this option to generate slightly smaller, faster code around static local declarations."
- Compile with
- Be aware that this option affects static local initialization semantics globally; it should only be used when thread-safety for static locals is not needed.
- Summary
-
thread_localobjects are implemented via TLS and initialized at thread startup; MSVC’s thread-safe initialization also uses TLS internally. - Heap allocation in a
thread_localconstructor during this phase can expose OS-level TLS or heap bugs or limits, leading to crashes on specific Windows builds. - Workarounds include restructuring initialization to avoid heap work in
thread_localconstructors and, where safe, disabling thread-safe initialization with/Zc:threadSafeInit-.
References:
- Storage classes
- /Zc:threadSafeInit (Thread-safe Local Static Initialization)
- Compiler Error C2482
- Compiler Warning (level 3) C4640
- A multithreaded application might crash in Windows 7 or in Windows Server 2008 R2
- Error:
allocation-size-too-big - Error:
heap-use-after-free - SQL Server 2022 CU18 Crashing with EXCEPTION_ACCESS_VIOLATION - Microsoft Q&A
- New AutoPilot Windows 11 build, OneDrive fails to create folder - Microsoft Q&A
- VS 2026 Error when running a .Net 10 console app in Debug - Microsoft Q&A
- Application crashing faulting module MSVC100.DLL - Microsoft Q&A
- Kernel_Security_Error BSOD. Driver Verifier causing bootloop. - Microsoft Q&A