Visual C + + exception (Exception) FAQ
Copyright: Doug Harrison 2001–2007
Translation: Magictong (Tong Lei) March 2011
Original address: http://members.cox.NET/doug_web/eh.htm
This article discusses some of the implementations in Visual C + + about try{} catch (...) and an unusual problem. This article uses a question-and-answer approach to the discussion, so if you read the full text as a whole, you will get more knowledge. To give you a general understanding of the discussion below, we can look at the list of issues first:
Q1 for the following code, I do not understand when I use release compilation mode or other compilation mode, but after using the optimization option (for example:/o1 or O2), why try{} catch (...) You cannot capture a structured exception for Win32.
Q2 is also the code above, which I don't understand is that if I use debug compile mode or compile option plus/EHa, Win32 's structured exception can be captured (SE). And, sometimes I find in release compilation mode, if you use the/GX compile option try{} catch (...) You can also capture Win32 (SE) structured exceptions. try{} catch (...) Not just support C + + exceptions?
Q3 if the try{} catch (...) What is the effect of capturing Win32 structured anomalies (SE) inside?
What is Q4 _set_se_translator?
Q5 How should I deal with these problems correctly?
Q6 in an MFC program, how should I safely use _com_error,std::exception, and other non-MFC exception classes?
This article applies to Visual C + + 5, Visual C + +. NET 2003, and subsequent versions. Upcoming Visual C + + 2005 (Translator Note: This article's writing time has been a few years, do not tangle this), codenamed "Whidbey", corrected the following discussion of a problem, and this part of the problem Q1, Q2 and Q5, they have been updated accordingly. Other questions and answers apply to Visual C + + 5 and later versions.
Q1 and for the following code, I don't understand when I use release compilation mode or other compilation mode, but after using the optimization option (for example:/o1 or O2 . ), Why try{} catch (...) We can't capture Win32 . structure of the exception.
#include <stdio.h>
int main ()
{
Try
{
int* p = 0;
*p = 0; Cause Access Violation
}
catch (...)
{
Puts ("Caught access violation");
}
return 0;
}
A, from Visual C + + 5 to the series of Visual C + +. NET 2003, if you compile with the/GX or/EHS compilation option, the meaning of these two compilation options is to turn on the compiler's synchronous exception mode. In this mode, only the exception thrown through the throw statement is captured, obviously there is no throw statement in the above code. If you examine the assembly code of this program carefully, you will find that the compiler optimizes the entire exception handling mechanism, because the compiler can determine that the code in the try does not throw a C + + exception. How awesome it is to optimize! This optimization is particularly effective when there is a large number of template code present. Unfortunately, there is a bug that causes try{} catch (...) in some cases to capture the structural exception of Win32, which leads directly to the next problem.
Q2 The same is the code above, which makes me very incomprehensible if I use Debug compile mode or compile option plus/EHa after the Win32 a structured exception can be captured (SE ). And, sometimes I find in release compilation mode, if you use the/GX compile option try{} catch (...) can even capture Win32 . (SE ) Structured exception. try{} catch (...) not just support C + + Is it unusual?
A, according to Stroustrup, C + + exception handling (EH) is not intended to handle signals or other low-level operating system special events, such as arithmetic anomalies. Win32 Structured Exception (SES) is explicitly classified into this category, in a sense,try{} catch (...) It is not possible to capture a Win32 structured exception. However, the C + + standard does not explicitly prohibit this behavior, and any time a structured exception (SE) is thrown is an undefined behavior, so it looks like try{} catch (...) capturing a structured exception (SE) is also a "legitimate" behavior. From a technical point of view, the C + + standard does not impose any requirements on this undefined behavior, such as indirectly referencing a null pointer. That is, using the try{} catch (...) to correctly capture all of the exceptions seems convenient, capturing a structured exception (SE) captures the root cause of a large number of problems, and before discussing why I say so, let's look at Visual C + + The documentation for this behavior is how to say.
Visual C++5 and its subsequent versions define two exception-handling models, called synchronous models and asynchronous models, respectively. The selection of the model can be controlled by the/eh compile command line,/EHS represents the use of the synchronization model, and/EHa represents the use of the asynchronous model. In MFC and some other application frameworks, a/GX compiler command line is defined by default, and/GX and/EHSC are equivalent, so it means choosing a synchronous model (c means that a function that is modified by extern "C" does not throw an exception). The VC + + documentation defines the asynchronous model in this way:
in Visual C + + in the previous version, C + + the exception handling mechanism by default is to support handling asynchronous exceptions (hardware exceptions), and in the asynchronous model, the compiler assumes that any one instruction can throw an exception.
The so-called synchronous and asynchronous exceptions my understanding is that the synchronization exception is the program in the throw statement to show the exception thrown, such as IO error, memory allocation failure, array out of bounds and so on, where the exception will be thrown, can be predicted in advance, and asynchronous exception is generally refers to the system hardware exceptions, access to null pointers, In addition to 0 errors and so on, this exception is completely unpredictable, in the VC debug mode the compiler will catch the asynchronous exception in the try statement, and the release mode will not. Of course these can be controlled by modifying the compilation options)
Under asynchronous model, try{} catch (...) It is possible to capture a structured exception, and if you really want to do this, you must set the Compile option/EHa. In addition, you can use the function _set_se_translator () to turn a structured exception into a C + + exception that captures a structured exception like a C + + exception, but you must also use the/EHa compilation option (refer to Q4).
The synchronization model can refer to the following description:
By Default, the new synchronization exception model can only be passed by the throw statement is thrown. Therefore, the compiler can make a hypothesis that exceptions can occur only when the throw statement is called or when the function is called. If the object's lifetime and function calls or throw statements do not overlap, in this model, the compiler can completely remove code that tracks the lifetime of the object in order to unwind operations like this, significantly reducing the size of the code.
The synchronous exception model was intended to support only C + + exceptions as Stroustrup said, but from Visual C + + 5 to Visual C + +. NET2003, this is not explicitly documented in the documentation, so if you compile without using optimizations or compile optimizations, but the optimizer cannot tell if a C + + exception will be thrown in a try statement, then try{} catch (...) It is still possible to capture a structured exception (SEs). For example, in VC5, if a function is called in a try statement, the optimizer assumes it might throw a C + + exception, but in VC6, the function may be in another compilation unit (source file), These make the optimizer feel confused (with popular words is the cup). Finally, in Visual C + +. These flaws in the synchronization model were corrected in NET2005.
Q3 if the try{} catch (...) Inside Capture Win32 Structured exception (SE ), what is the impact of it?
To answer this question, we need to first understand the features and characteristics of C + + exceptions and structured exceptions (SEs). According to Stroustrup, C + + exception handling is error handling. For example, when you request memory failure or when you write a file with insufficient disk space, the best way to notify these errors is to throw an exception, especially if these resources are sufficient in normal circumstances. This method greatly simplifies the code that needs to check the return value of the function, and it allows you to focus on some errors. Such errors are most likely to occur in a correct program (this is not a program bug, but a predictable error), and this is exactly what C + + exception processing is intended to achieve.
On the other hand, structured exceptions (SES) typically identify a typical program bug. You should be familiar with the violation of access violations by referencing a null pointer. The hardware can detect this error and be able to capture it (creating an interrupt), and then Windows translates this special hardware event into a structured exception (SE). A structured exception basically indicates a problem with the program writing, and a correct program should not produce this error. SEs (structured exception) is also used for some of the normal operation of the system, for example, you use VirtualAlloc () in memory to request an address space and dynamically open access to some pages, it is possible to make the program access to an unauthorized memory address, resulting in a page error. Using the __try __except Statement program, you can capture this exception (SE) and unlock the memory access, and then resume the execution of the program from the instruction that caused the exception. C + + exceptions are not possible because they cannot interfere with this behavior.
C + + exceptions and structured exceptions to Win32 are completely different things. If a consistent capture in try{}catch (...) causes the following problems:
1. If try{}catch (...) can capture a structured exception, you may be confident in writing the following code:
Begin Exception-free Code
... Update Critical data structure
End Exception-free Code
If the critical code snippet has a bug and causes a structured exception (SE), the outer try{}catch (...) block may catch this exception, but this will lead to a completely unexpected state flow of the entire program. The program may then move forward like a runaway aircraft, resulting in greater damage. Fortunately, an uncaught structured exception that is thrown after a serious disruption causes the program to terminate abnormally. However, if try{}catch (...) does not capture the first structured exception, the program may be easier to debug, because the final cause of the program Crash (SE) may be far from where the bug really is. The operating system will report this uncaught structured exception, giving you the opportunity to debug the program, but in this case it may take you to the source code location where the last exception (SE) occurred, not where the actual problem occurred.
2, the following code is more puzzling ...
Try
{
Thefastbutresourcehungryway ();
}
catch (...)
{
Theslowbutsureway ();
}
In the try code block, if the program has a bug or the compiler generated code has a bug that caused an access violation (access violation), because try{}catch (...) The capture of structured exceptions makes it impossible to find this bug. And its only performance may be extremely slow to run, and it does not necessarily appear in the test. If try{}catch (...) do not catch this exception, you will certainly find this bug in the test phase (the operating system's classic Cue program runs abnormally terminated the MessageBox will pop out unexpectedly).
3, the normal operation of the system may be affected or even destroyed, for example, MFC's CPropertySheet::D Omodal () in the document explicitly said do not use Try{}catch (...) in this function. DebugBreak (API) throws an exception that could be captured by Try{}catch (...) , causing DebugBreak to lose its effect. Similarly, if you are using __try __except to capture a structured exception, and the internal code has a try{}catch (...) code present, you may get into trouble even if the internal try{}catch (...) The code throws the exception back, and if your structured exception handler wants to resume execution from the wrong command, you'll almost be in for a lot of unexpected trouble, because you'll find that the catch (...) code block has been entered and the local variable has been completely destroyed, and the more cups are if the recovery is performed where the catch (...) block that matches the try{} code block, and then this try{} code block throws a C + + exception, you will be frustrated when you find that the structured exception handler is trapped in an infinite call dead loop. This place is a little vague in the original sense. Interested can refer to the original text, that is probably the meaning. As I understand it, the first __try/__except statement is an expression in the () after __excep , and the return value of the expression can be one of the following three:
exception_continue_execution (–1) indicates that the exception is ignored and the control flow resumes running at the point at which the exception occurred.
exception_continue_search (0) indicates that the exception is not recognized, which means that the current __except The module is not the correct exception handling module for this exception error. The system will continue to find an appropriate __except module in the __try/__except domain of the previous layer .
exception_execute_handler (1) Indicates that the exception has been identified, that is, the current exception error, the system has been found and can confirm that the __except The module is the correct exception handling module. The control flow is entered into the __except module.
if the structured exception handler returns-1 , that is, you want the program to resume execution at the exception point, is actually a risky behavior, it is possible to repeat the exception, and then go into the exception handler again, and then the function returns-1 , and then resume execution ... So the dead loop is here. It is very rare for individuals to feel fit for exception_continue_execution (–1) . )
4. Application framework using try{}catch (...) To prevent the user's code from becoming a risky behavior (based on the above 3 points), which is usually the case. For example, if the MFC framework does not use try{}catch (...) To protect itself, the result may be that an uncaught C + + exception in user code causes the entire MFC application to terminate abnormally.
Q4 _set_se_translator What is it?
A, _set_se_translator is a function provided by the C + + runtime library, which can be used to register a callback exception conversion function, which can be used to convert a Win32 structured exception into a real C + + exception. It allows you to partially avoid the try{} catch (...) described in Q3. Problem, such as you can write the following code, where se_t is the type of exception object thrown by the conversion function.
catch (se_t) {throw;}
catch (...) { ... }
This is not a particularly good solution because you can easily forget to add code like the one above in each try{} catch (...) . If you want to use this method, you have to set the callback exception conversion function before each thread starts executing the code, and each structured exception handler is just a property of one thread, and you call the _set_se_translator function in one thread without affecting another thread, and, The callback exception conversion function cannot be inherited by the new thread, so the thread created after the call to _set_se_translator has no effect on the thread. In addition to being difficult and error-prone in implementation, this workaround cannot be responsible for code that is neither written nor modified by you, which may be a problem for the user of the library.
Ultimately, the document is not clear about whether the _set_se_translator function can be relied upon, and you have to choose the asynchronous exception-handling model discussed in Q2, where your target code expands rapidly. If you do not, your code may be optimized as discussed in Q1.
Q5 How should we deal with this?
A, if you are using Visual C + + 5 to the version of Visual C + +. NET 2003, the best way is to avoid using try{} catch (...) as much as possible, and if you have to use it, you'd better be aware of some of the issues discussed above. In Visual C + +. NET 2005, however, the behavior of the compile option/EHs is consistent with what is described on the document, and the behavior of the synchronous exception model is normal, and you no longer have to worry about try{} catch (...) to catch a structured exception (SEs).
Q6 in an MFC How to use _com_error safely in a program , Std::exception , and other non-MFC Exception class?
A, before Visual C + + supports C + + exception handling, MFC has been designed, MFC's most primitive exception design is macro-based, such as try and catch, these macros use the setjmp and LONGJMP functions to simulate C + + exception handling. To simplify the implementation, in earlier versions of MFC, it was only supported to throw a cexception pointer type, such as a pointer to a CException object, or a pointer to an object derived from CException class. Even after MFC was updated to Visual c++2.0 and started to support C + + exception handling, no other exception types were ever supported, and MFC's source code continues to use this set of macros, but now it is defined using C + + exception handling, for example, in MFC, It defines Catch_all as:
catch (cexception* e)
Obviously, if you use the C + + standard library, COM libraries, or other external libraries in the try code block, and so on, some libraries that define your own exception types, this is not a way to catch all C + + exceptions. In addition to using the cexception* exception type, MFC does not use any other exception type, so in many cases your code may be packaged as follows:
TRY
{
Call your code
}
Catch_all (e)
{
Perhaps report, the error to the user
}
End_catch_all
For example, MFC's window procedure functions are protected in this way, because exceptions are not allowed across Windows ' message boundaries. However, Catch_all also captures only MFC exceptions, and if a non-MFC exception catch fails, your program may terminate abnormally because of an uncaught exception. Even if you capture these anomalies yourself, it is important to capture them somewhere, because there are many functions in MFC that want to capture all the exceptions, and then they can do some cleanup work or return an error code to the caller via a return statement. Now, if the function inside the try block calls into your code, and your code does not immediately translate a non-MFC exception into an MFC exception, that is, you allow a non-MFC exception to propagate in the MFC code, and want to capture them all, but as you just said, (MFC) does not capture it, and it does not capture it. Even if your non-MFC exception is caught on the same level of external code, it may be a bit late, and you may end up skipping some important cleanup code. All of these indicate that we should follow the following rules:
prohibit non-MFC exception in MFC code propagation (never allow a NON-MFC exception to pass through MFC code )
At a minimum, the use of try{}catch (...) also protects each message handler from being able to exit the program in a friendly time when it encounters a non-MFC exception. If a message handler cannot process an exception and wants to report the exception to the user, a friendly exit procedure might be more appropriate for the handler. If MFC can catch this exception, MFC will present a more friendly MessageBox to the user to describe the error. To achieve this, you need to convert a non-MFC exception into an MFC exception, which you can do with macros (macro). For example, you can look at the following code:
Class Mfcgenericexception:public CException
{
Public
CException Overrides
BOOL GetErrorMessage (
LPTSTR Lpszerror,
UINT Nmaxerror,
Puint pnhelpcontext = 0)
{
ASSERT (Lpszerror! = 0);
ASSERT (Nmaxerror! = 0);
if (Pnhelpcontext! = 0)
*pnhelpcontext = 0;
_tcsncpy (Lpszerror, m_msg, nMaxError-1);
LPSZERROR[NMAXERROR-1] = 0;
return *lpszerror! = 0;
}
Protected
Explicit mfcgenericexception (const cstring& msg)
: M_msg (msg)
{
}
Private
CString m_msg;
};
Class Mfcstdexception:public Mfcgenericexception
{
Public
Static mfcstdexception* Create (const std::exception& EX)
{
return new Mfcstdexception (ex);
}
Private
Explicit mfcstdexception (const std::exception& EX)
: Mfcgenericexception (Ex.what ())
{
}
};
#define Mfc_std_eh_prologue try {
#define Mfc_std_eh_epilogue/
} catch (std::exception& ex) {throw mfcstdexception::create (ex);}
The above code defines a class: Mfcgenericexception, which inherits from MFC's CException, And it is the base class for other non-MFC exception classes such as Mfcstdexception (the reason we need this base class is that MFC does not provide a generic exception class that encapsulates the exception message string). The macro defined at the bottom is intended to facilitate the inclusion of code that might throw a non-MFC exception, such as your message handler function or other code that is called by MFC. You can use them like this:
void Mywnd::onmycommand ()
{
Mfc_std_eh_prologue
... your code which can throw std::exception
Mfc_std_eh_epilogue
}
Here, the two macros take your code to include the try code block, and the Mfc_std_epilogue macro also converts the Std::exception exception type to the type of exception that MFC can catch, where it is converted to mfcstdexception. Note that Mfcstdexception has a private constructor and defines a static create function, which provides the ability to create a Mfcstdexception object on the heap.
In summary, these macros protect your code through a try code block, and the Mfc_std_eh_epilogue macro transforms the std::exception exception into an MFC exception re-thrown, which is converted to an Mfcstdexception exception object. It is important to note that mfcstdexception this exception class has only one private constructor and defines a static create function, which provides the ability to create a Mfcstdexception object on the heap. This ensures that the exception class can only be created on the heap, which is exactly what we need because each object maintains a false state information, and if we simply throw a pointer to a static object like AfxThrowMemoryException, thread safety is a big problem, This can happen when the thread is throwing and handling an exception, and another thread throws an exception, and the last exception that is thrown may overwrite the previous exception information. In this case we have no short cut! Whoever catches an exception is responsible for invoking the Delete member function inherited from CException by the exception object. The function is to delete the Mfcstdexception object, by prohibiting the creation of the local exception object, it is good to avoid throwing the local exception object pointer error behavior.
In the case of MFC programming, it is necessary to use a technique similar to the one above if you want to maintain the robustness of the program by mixing various types of exceptions. This is easier to capture than just writing try{}catch (...), and it throws an exception to the block of code that is best suited to handle the exception. In fact, in a well-designed program to write directly Try{}catch (...) it is rare that code written in this way can be used to ensure that the program is correct by automatic destruction of the stack (unwinding) and local variables. Therefore, the last step of exception handling is often simply to tell the user where the problem, by the non-MFC exception to the MFC exception, MFC can easily fix the last processing O (∩_∩) O.
Comments
If you have any questions about this article, you can write to:[email protected] or [email protected].
http://blog.csdn.net/magictong/article/details/6256685
Visual C + + exception (Exception) FAQ (original title: A Visual C + + Exception FAQ)