TL,DR;
Exception handling can cause memory leaks. Your C++ compiler is smart enough to realize the possibility of memory leaks surrounding exception handling and is automatically inserting exception handlers where necessary to prevent memory leaks. This means that there really is a try/catch block where you observed an exception handler, except it is handling nothing and simply re-throwing the exception after cleaning up C++ objects.
how does the system know if there's an exception handler for a function and to distinguish the function which have no try/catch from those which have it
The system doesn't know. In fact, it is impossible for the system to know, so it simply walks down the chain of exception handlers until it finds one that tells the system "exception handled". This means that whenever you see those 3 assembly instructions, there is always an exception handler and I believe that it should always be possible to reverse the exception handler into an equivalent C++ try/catch block.
But as you have observed, not all exception handlers need to be explicitly defined. Your C++ compiler will insert them into your program where necessary for the purpose of object cleanup, because exception handling can cause memory leaks:
void i_leak_memory() {
void* memory = malloc(512);
throw std::exception();
// this line of code will never be executed
free(memory);
}
void i_handle_exceptions() {
try {
i_leak_memory();
}
catch(std::exception e) {
// exception handled
}
}
In the above code, you will find that memory is never freed due to the interruption caused by the exception. At the exact moment the exception occurs, execution is immediately transferred to the catch in i_handle_exceptions()
and after handling the exception, execution continues in i_handle_exceptions()
. i_leak_memory()
never gets an opportunity to release resources after the exception occurred, and so we end up with a memory leak.
Fortunately, there is a simple solution to this apparent memory leak. Simply replace i_leak_memory()
with i_do_not_leak_memory()
:
void i_do_not_leak_memory() {
void* memory = 0;
try {
memory = malloc(512);
throw std:exception();
free(memory);
}
catch(std:exception e) {
free(memory);
throw;
}
}
By simply adding a try/catch block to the function, we can give it one last chance at releasing resources in the event of an exception. The catch block does not necessarily need to handle the exception, but it allows us to include special logic that would prevent memory leaks.
With the RAII design of C++, the task of object cleanup is the responsibility of the C++ compiler. When using throw()
, the C++ compiler needs to inject special logic into the code (possibly in multiple functions) to prevent memory leaks from occurring. So in your observations, what really happened is that your C++ compiler replaced i_leak_memory()
with i_do_not_leak_memory()
because if it didn't, the program would be leaking C++ objects whenever an exception occurred.