In any modern operating system the virtual memory pages feature is used. In this model each process owns an entire address space and the system has the responsibility of mapping the address that the process wants to read to the address where that memory actually is. This gives the system several useful optimization opportunities, such as putting unused pages on the disk (swap), rescuing them when they are required, or applying copy-on-write (CoW) where two processes share the same page. >
In this way the system already knows exactly which memory belongs to each process. And releasing that memory is as simple as marking it as a garbage page that can be reused in another process. That is, for any modern computer, ending a process means releasing all of its memory.
Considering a large program, there are several problems when you press "close". The destructor of all objects will be executed in sequence, causing a lot of half-memory to be accessed and read from the disk (if any swap occurred) and causing visible slowness to the end user. The HD life is spent while wasting user time, all wrong. This just to release features that will already be released by the system anyway, and more efficiently (it frees pages instead of releasing object by object, and does not need to read data from disk).
Whether this is good practice is questionable. The object-orientation design hopes that everything that is built will, at some point, be destroyed. Then there may be some important logic going on in the destructor, like saving settings to disk. In this case merely calling
exit(0) can be harmful. Another way is to have a Boolean global variable that is true when the process is about to close and have each destructor check this variable, bypassing unnecessary memory releases. But here there is little advantage because a lot of memory will need to be read anyway (we go back to the swap) and the logic of destruction gets complicated and it's not trivial.
One solution may be to have a notification engine that sends some kind of signal to all objects that need to do something else at the end of the program. It can be a list of callbacks for example. This is probably the most efficient way that does not completely break the object-oriented structure.
The big problem is that tools that detect leak will accuse a lot of problems in your program. Hence it is interesting to have two termination paths, one deallocating everything (for debugging) and another going straight to
exit(0) . The two must be equally tested, of course. But the second does not have to go through the leak detector.
But remember that there are other features than memory. The interprocessor communication mechanisms of System V (available in some * nix, including linux), for example, are permanent and survive the termination of the by-design process. It is essential for them to be released.
All this discussion is only valid for languages where you programmer does memory management. For garbage collector languages there is usually not much choice.