When are global objects constructed and destructed by Visual C++?


Today we're going to fill in the following chart:

When does it run? Constructor Destructor
Global object in EXE
Global object in DLL

The C++ language specification provides some leeway to implementations on when global static objects are constructed. It can construct the object before main begins, or it construct the object on demand according to complicated rules. You can read [basic.start.init] for the gory details.

Let's assume for the sake of discussion that global static objects are constructed before main begins.

For global objects in the EXE, constructing them is no big deal because the C runtime startup code linked into the EXE does a bunch of preparation before calling the formal entry point, be it main or wWin­Main or whatever. And part of that preparation is calling constructors for global objects. Since the C runtime startup code is in charge, it can construct the objects right there.

When does it run? Constructor Destructor
Global object in EXE C runtime startup code
Global object in DLL

DLLs are similar: The formal Dll­Main entry point is not the actual entry point to the DLL. Instead, the entry point is a function provided by the C runtime, and that function does work before and after calling the Dll­Main function provided by the application. We saw this earlier when we discussed what happens if you return FALSE from DLL_PROCESS_ATTACH.

Part of this extra work done by the C runtime library is to construct DLL globals in DLL_PROCESS_ATTACH and to destruct them in DLL_PROCESS_DETACH. In other words, the code conceptually goes like this:

BOOL CALLBACK RealDllMain(
    HINSTANCE hinst, DWORD dwReason, void *pvReserved)
{
  ...
  case DLL_PROCESS_ATTACH:
   Initialize_C_Runtime_Library();
   Construct_DLL_Global_Objects();
   DllMain(hinst, dwReason, pvReserved);
   ...

 case DLL_PROCESS_DETACH:
   DllMain(hinst, dwReason, pvReserved);
   Destruct_DLL_Global_Objects();
   Uninitialize_C_Runtime_Library();
   break;
 ...
}

Of course, the actual code is more complicated than this, but that's the basic idea. We can fill in two more cells in our table.

When does it run? Constructor Destructor
Global object in EXE C runtime startup code
Global object in DLL C runtime DLL_PROCESS_ATTACH prior to Dll­Main C runtime DLL_PROCESS_DETACH after Dll­Main returns

The last entry in our table is the tricky one: Who triggers the destruction of global objects in the EXE destructed? The C runtime startup code in the EXE is guaranteed to run at process startup, but how does the C runtime cleanup code run?

The answer is that the C runtime library hires a lackey. The hired lackey is the C runtime library DLL (for example, MSVCR80.DLL). The C runtime startup code in the EXE registers all the destructors with the C runtime library DLL, and when the C runtime library DLL gets its DLL_PROCESS_DETACH, it calls all the destructors requested by the EXE.

That's the final cell in our table.

When does it run? Constructor Destructor
Global object in EXE C runtime startup code C runtime DLL hired lackey
Global object in DLL C runtime DLL_PROCESS_ATTACH prior to Dll­Main C runtime DLL_PROCESS_DETACH after Dll­Main returns

You can now answer this customer question and explain the observed behavior:

Is it okay to call Load­Library from within constructors of global C++ objects inside a DLL? Currently we am seeing weird behavior when doing so.

The customer went on to describe what they were observing. Their DLL has global C++ objects which do the following operations in their constructor:

  • Check a setting.
  • If the setting is enabled, call Load­Library to load a helper DLL, then call a function in the helper DLL, The result of that function call alters the global behavior of the original DLL.

  • The function in the helper DLL creates a thread then waits for the thread to produce a result.

  • The helper thread never gets started.

Result: Process hangs.

Comments (23)
  1. Lars says:

    It is not okay to call LoadLibrary in a global constructor, because the loader lock is held at the time (cf. countless discussions of DllMain in this blog)

  2. Graham says:

    For global objects in an exe, what about programs that are (at least, according to the compiler / linker options) statically linked to the C runtime? Do they still actually rely on a separate DLL, or is there another method used in this case?

    Actually, why is the lackey necessary? Since the CRT ultimately calls main(), couldn't it just destruct the global objects once the call to main() returns?

  3. ZLB says:

    @Graham: Not all processes ever return from main(). What about processes that use exit() or ExitProcess()?

  4. Sven2 says:

    @ZLB: exit is also in the C runtime so it could just call the destructors. On ExitProcess, global destructors aren't called anyway, are they?

  5. Hildar says:

    ExitProcess() documentation says it sends DLL_DETACH to all .dlls. which includes the C runtime DLL.

    So yes, global destructors should get run. ((And returning from main just calls ExitProcess))

  6. Damien says:

    @Sven2 - are you confusing ExitProcess (which does allow shutdown processing, including global destructors, to occur) and TerminateProcess (which doesn't)

  7. Anonymous2 says:

    What happens when you link with libcmt (/MT)?

  8. Daniel says:

    > Is it okay to call Load­Library from within constructors of global C++ objects inside a DLL? Currently we am seeing weird behavior when doing so.

    Much more critical is:

    Is it okay to call FreeLibrary from within destructors of global C++ objects inside an EXE?

    From what Raymond said, I'd say no... (And no... I didn't knew that)

    Since we're destroying the app anyway, it technically isn't necessary to call FreeLibrary anyway (I guess windows will do that anyway)

  9. ZLB says:

    "Is it okay to call FreeLibrary from within destructors of global C++ objects inside an EXE?"

    Since its not ok to call FreeLibrary in DllMain you shouldn't.

    This is a good blog post though as process shutdown is quite a complex subject with lots of hidden gotchas!

  10. I wouldn't exclude that the customer put the LoadLibrary inside the constructor of a global object in a misguided attempt to work around the restrictions of not using LoadLibrary inside DllMain - I've seen this kind of reasoning in the past on SO (stackoverflow.com/.../214671).

  11. Myria says:

    Is it legal in .exe files linked to the .lib version of the Visual C++ CRT to have a TLS callback that calls _c_exit() on DLL_PROCESS_DETACH?  (Assuming that _c_exit is what I meant; I mean the function that triggers global destructors.)

  12. Tomashu says:

    I had put breakpoint in destructor and it get called by onexit terminator inside doexit. Not from some dll.

  13. jonwil says:

    I have 2 binaries here, one is a .exe compiled with Visual C++ 2013 and linked to the DLL CRT.

    The other is a .exe compiled with Visual Studio 6 and statically linked.

    In the case of the dynamically linked exe, the up-stack from the destructor of a global object is the function doexit in msvcr120d.dll then exit (also in msvcr120d.dll) then __tmainCRTStartup in my executable (i.e. the entry point to the program)

    In the case of the statically linked exe, the up-stack is the same (doexit then exit then the the CRT startup function

    So the whole "the C runtime dll uses its dllmain to call destructors" thing is incorrect...

  14. Harry Johnston says:

    @jonwil: interesting, but what happens if main() calls ExitProcess rather than returning?

  15. Visitor says:

    Is it legal to use "am" after "we"?

  16. Random832 says:

    How does it stop them from running if _Exit, quick_exit, or abort are called (all of which explicitly do not cause static destructors to run)?

  17. @Harry Johnston says:

    "what happens if main() calls ExitProcess": Why should the C runtime care? ExitProcess() is not part of the CRT API. It's just one of many OS calls where you're on your own. I think ExitProcess should terminate the process the same way as TerminateProcess(GetCurrentProcess) would do (regarding C objects).

  18. GregM says:

    A few years ago I got hit by one of these "Dll contains a global object whose constructor tries to load a DLL" hangs in a binary-only third-party component.  The component had a global object that did something with winsock, and that winsock function tried to load a DLL.  This only happened on machines at one particular company, it had something to do with how they had their network set up.  We never did figure out exactly what it was.  Fixing that was not fun, since I couldn't get any help from the component supplier.  I ended up having to hook that call to the winsock function and basically make the call not happen.

  19. Zack says:

    If memory serves, C++2011 and possibly also C++1999 require all destructors for static-storage-duration objects in the EXE (neither of these standards have a concept of DLLs, so to them there is no other kind) to be executed as-if <tt>atexit(curry(destructor, object))</tt> had been called for each such object immediately after its constructor succeeds.  This is observable by (in the simplest case) a program that has one file- and one function-scope static object, both with destructors, and also registers a plain atexit() callback from main() before calling the function containing the function-scope static object.  The plain callback must execute after the function-scope object's destructor but before the file-scope object's destructor.

    As such, I am curious whether and how MSVC's handling of atexit callbacks is different from its handling of destructors.

  20. SimonRev says:

    Zack,

    A few years ago I had reason to play with atexit and as I recall, all the global objects had their desctructors registered with atexit.  

    [Precisely. MSVC uses atexit to drive destructors. That's why the action is triggered by the C runtime DLL: Because the C runtime DLL is where exit functions are triggered. -Raymond]
  21. SimonRev says:

    Funny, when I was looking at the atexit stuff, I was fairly sure that the atexit routines were driven by a return from main/WinMain.  Of course I had statically linked the CRT in that project.

  22. alegr1 says:

    [Precisely. MSVC uses atexit to drive destructors. That's why the action is triggered by the C runtime DLL: Because the C runtime DLL is where exit functions are triggered. -Raymond]

    Last time I checked, atexit actions only run on exit() or return from main(). Why bother supporting ExitProcess which requires registering for DLL_PROCESS_DETACH, if atexit contract doesn't require it? exit() can call those atexit handlers.

    [You're saying that MSVC should not bother supporting anything beyond the minimum required by the standard? I suspect that there are apps which rely (perhaps inadvertently) on ExitProcess running global destructors. -Raymond]
  23. John Doe says:

    We come to the conclusion that your app, or whatever, should be in a DLL, and EXEs should just be stubs that depend on it and call a specific function with specific arguments, probably parsed from the command line.  If the command line is not parsed by the DLL itself, that is, since some common entry points already may do so, such as DllInstall and anything made to be invokable from rundll32.exe.

Comments are closed.

Skip to main content