If you return from the main thread, does the process exit?


If instead of calling ExitProcess you merely return from the main thread of a process, does the process terminate?

No, but maybe yes.

This is another one of the places where the C runtime behaves differently from raw Win32.

Under raw Win32, a process exits when any thread chooses to exit the process explicitly (usually by calling ExitProcess) or when all threads have exited. Exiting the main thread will not result in the process exiting if there are any other threads still active. According to the old-fashioned model of how processes exit, a process was in control of all its threads and could mediate the shutdown of those threads, thereby controlling the shutdown of the process. (Of course, nowadays, with the thread pool, COM worker threads, and other threads doing random background work, the idea of being in control of all the threads in the process is now just a reminder of those simpler days.)

On the other hand, the C runtime library automatically calls ExitProcess when you exit the main thread, regardless of whether there are any worker threads still active. This behavior for console programs is mandated by the C language, which says that (5.1.2.2.3) "a return from the initial call to the main function is equivalent to calling the exit function with the value returned by the main function as its argument." The C++ language has an equivalent requirement (3.6.1). Presumably, the C runtime folks carried this behavior to WinMain for consistency.

This also means that if you decide to exit your main thread by calling ExitThread directly, then you aren't returning from the main function. Instead, you've leapt into the Win32 world where the process will not exit until all threads are gone.

Comments (19)
  1. Wyatt says:

    I didn't think a background thread would keep a process alive, that the process was terminated when all non-background threads finished.

  2. Leo Davidson says:

    @Wyatt: I don't think Win32 has the concept of background threads; they're all just threads.

  3. exiter says:

    @JM: System components do not usually "spawn additional threads in your process for whatever reason"

    Yes, they do. One example is: You cannot write a .net program which executes with less than 5 threads. There's no guarantee that forthcoming .net versions doesn't have 50 threads.

    And the "do return from main function exit program?" is indeed in the standard, as raymond wrote.

  4. I don't think Win32 has the concept of background threads

    Maybe not, but programmers do.  If an injected thread delays the termination of the process for any appreciable time, that's kind of rude.

    Also, it seems, background threads in a C-runtime-library app need to be ready at all times for an ExitProcess!

  5. Ens says:

    @exiter

    Well, we were talking about raw Win32 and the C runtime.  I don't think .NET is a system component.  Rather, Win32 is a component of the .NET system.  Of course, the term "system component" is itself sort of nebulous.

  6. Confused says:

    @Ens:

    Win32 is a component of the .NET system.

    That's like saying "I'm father of my mother". I don't have .NET, but I have Win32, explain that!

  7. Ivo says:

    That's like saying "I'm father of my mother".

    There was an episode of Futurama last night where that was indeed the case! It involved time travel to 1947…

  8. No One says:

    @Confused:

    Win32 is a component of the .NET system in that .NET requires Win32 in order to run.

    Just like my engine is a component of my car.

  9. Nicole DesRosiers says:

    This is .NET specific, but on topic.  Before I knew about the powers of BackgroundWorker, I used normal threads to handle my background processes.  When I detected a close on the UI thread, I sent an event to the worker thread telling it to finish what it was doing as fast as possible, then write out the in-memory data to storage.  After that, it exited.  This produced the effect of the UI closing immediately, but the background thread hung around for a second or two more to complete its post-processing and exit.

    With BackgroundWorker, if the UI closes, the BackgroundWorker thread is terminated immediately.  This makes some sense based on how it works, because the BackgroundWorker can't be marked as completed until an event is handled on the UI thread.  As a side effect, this also means you can't spin on the UI thread (yes, I know this is a bad practice) waiting for the worker to complete, because you'll never handle the event, and the BackgroundWorker will never report completion.  I ended up catching the Closing event, cancelling the Close, setting a flag, waiting for the BackgroundWorker to complete, and then calling Close myself.

    This has been your random .NET digression.  Please excuse any terminology mistakes I may have made — I don't write production UI code.

  10. f0dder says:

    Raymond, "Production" is a relative term in the context of assembly programmers – most of their stuff obviously isn't going to reach a super wide audience. Some of them do tend to think that doing these things is just fine and won't ever break… and pass off their observations of how current systems work as gospel.

    Tricks like back-scanning for kernel32 from entry-point ESP is fine if you're writing a 4kb intro, and I can understand why malware authors do whatever they can to keep code size down and be tricky – but I'm not a fan of depending on undefined behavior, even if it works on all current Windows versions. Remember what happened to programs doing stack-execution when DEP was introduced?

  11. f0dder says:

    Raymond, "Production" is a relative term in the context of assembly programmers – most of their stuff obviously isn't going to reach a super wide audience. Some of them do tend to think that doing these things is just fine and won't ever break… and pass off their observations of how current systems work as gospel.

    Tricks like back-scanning for kernel32 from entry-point ESP is fine if you're writing a 4kb intro, and I can understand why malware authors do whatever they can to keep code size down and be tricky – but I'm not a fan of depending on undefined behavior, even if it works on all current Windows versions. Remember what happened to programs doing stack-execution when DEP was introduced?

  12. f0dder says:

    Ah, this is one of the topics that come up once in a blue moon on assembly programming forums. Can you shed some light on whether it's safe simple to RETurn from the entrypoint, rather than doing ExitThread/ExitProcess, from a *raw* Win32 program? It works for simple tests on all windows versions I know of, but is there any *guarantee* that a program's main thread is going to be "just another thread"? Or that some system component won't spawn additional threads in your process for whatever reason, once you start using some of the richer APIs?

    An example of such a discussion is board.flatassembler.net/topic.php .

  13. JM says:

    @f0dder: there is no "main thread" in Win32 — the MSDN does mention the "primary thread", which is a better name as it emphasizes that it's just the thread that happened to be executing the entry point code, which is not special in any other way. Win32 processes exit when the last remaining thread exits, it's as simple as that. Returning from the entry point does *not* exit the process, it exits the thread. If the thread running the entry point code also happens to be the last thread in the process (the common case), the process will exit. This is documented in the MSDN under "Terminating a Process".

    System components do not usually "spawn additional threads in your process for whatever reason" (you have to call particular functions for those threads to appear) but they are usually also not very explicit in telling you when they're going to be creating threads that could potentially interfere with process termination. Hence Raymond's wistful remark about the simpler days — everyone and their mother creates helper threads these days, many of which simply don't terminate by themselves, requiring an ExitProcess() call. Although it's overly paranoid to claim that you *must* call ExitProcess() because of a hypothetical rogue thread creator (it's not completely beyond control, especially in a toy app), it's also silly not to simply call ExitProcess() to be sure of termination (unless you're hunting down bugs with threads not properly shutting down when they should). It depends on your idea of "safe", I guess.

    As for guarantees, you'll find that Win32 doesn't do a lot of that. Between the MSDN and Windows Internals, you're supposed to figure it out for yourself if there's any gaps left. As the number of different Win32 platforms is limited (I mean, limited as compared to something like C systems) it usually works out, but there's no formal Win32 standard.

  14. Anonymous Coward says:

    But if you exit a process while there are still threads running, that means that you're terminating them while they could be in the middle of something.

    So you can't really use ExitProcess because you can't know (in principle) what extra threads might exist that might still be doing something important.

    From that it follows that threads must always in some fashion terminate themselves when they're done, either through logical control flow (there's no more work to do – exit) or by listening for an explicit signal, in which case the documentation of whatever call created the thread should specify that.

    Everything else is simply bad design.

  15. f0dder says:

    @JM: well, I'm not too interested in toy apps – showing a MessageBox and doing a RET doesn't really prove anything. I'm concerned about whether simply doing RET in "real-world" programs is sane. And yes, there's people writing relatively large things in pure assembly (let's not debate whether it's sane, but iirc http://www.oby.ro/rad_asm/ is entirely asm code).

    What happens once you start using nontrivial COM stuff? Printing stuff? (some printer drivers apparently do crazy stuff; iirc Jeremy Collake told me he had to disable some process protection stuff in PECompact because of printer drivers) – et cetera.

    I'm not a fan of people making too many assumptions on undocumented internals. Sure, dword@[ESP] is a return-value into kernel32 on all Windows versions I've seen, and alle modules load at 64kb boundary on all systems I've seen, but does that mean it's a safe way of geting kernel32.dll module load base? And that you can safely make executables with no imports?

    (The answer is no: at least one windows version (win2k) doesn't automatically inject kernel32.dll, and will *silently* fail loading an executable that doesn't import from kernel32.dll (*somewhere* in it's chain, an explicit reference is not necessary; Iirc the import with the shortest name satisfying a kernel32.dll reference is GDI32!Arc).

    [Are these people doing stunts like this for personal amusement or for production? It's fine if you're just toying around, but if you're writing production code, then just do things the normal way. If you want kernel32, then link to kernel32 rather than pulling crazy indirect tricks to try to infer the location of kernel32. The person who has to maintain your code will thank you. -Raymond]
  16. Dan Bugglin says:

    @Wyatt .NET allows you to flag threads as "background" or not when you start them… non-background threads block process termination, background ones are terminated once the main thread terminates.

  17. Ben Hutchings says:

    "This behavior for console programs is mandated by the C language…"

    This is nonsense. The C and C++ standards mandate a single-threaded execution model. The behaviour of multithreaded programs is completely outside of the scope of these standards (though not for much longer).

  18. Anonymous says:

    @Ben Hutchings

    I also was aware that the standards say nothing about threads.  That's why when I saw this, I read that part as, "the standards require that returning from main() have the same behavior as calling exit()".  Nothing about threads in that statement either.

    But in Windows, the C runtime's exit() calls Win32 ExitProcess(), which has this behavior re: threads.  So for returning from main to have the same behavior as calling exit(), they have to either (1) make exit() not terminate all threads, or (2) terminate all threads.

    Raymond provides citations for which part of the standard he's talking about, so maybe that would clarify this point.  (Personally I don't have much interest in looking it up, but it sounds like you might.)

  19. dave says:

    @Ben Hutchings

    C++ ISO/IEC 14882:2003, 3.6.1 main function, para 4

       Calling the function void exit(int) .. terminates the program …

    This certainly sounds like a requirement to terminate other threads, even though the language has no concept of threads.  It is difficult to imagine how you could satisfy 'terminate the program' otherwise.

    Para 5 says that returning from main() is equivalent to a call to exit().

Comments are closed.

Skip to main content