C + + exception mechanism implementation and overhead analysis (large graph, the compiler will add EHDL structure for each function, form a one-way list, very famous "Memory Access violation" error dialog box is a manifestation of this mechanism)

Source: Internet
Author: User
Tags unique id

Poplar

http://baiy.cn

 

When I started writing "C + + coding specifications and guidance" a few years ago, I planned to add an article to discuss the C + + exception mechanism. I did not expect a few years after the opportunity to finish the tail:-).

or the opening line: "Using the right features on the right occasions" is a basic standard for every C + + programmer who is competent. To do this, you have to understand how each feature in the language is implemented and how it spends its time and space. Exception handling, which involves a lot of underlying content, has always been a hard-to-understand and well-mastered part of C + + advanced mechanisms. This article will discuss this new feature in C + + and analyze its implementation overhead with minimal introduction of the underlying details:

    • About Threads
    • Call and return of functions
    • Call and Return of C + + functions
    • Stack fallback (stack unwind) mechanism
    • Exception trapping mechanism
    • Exception throws
    • Structured exception handling in Windows
    • Overhead analysis of exception handling mechanism
    • Section
    • Related documents:
      • C + + coding specification and guidance
      • Implementation methods, overhead analysis and usage guidance of RTTI, virtual functions and virtual base classes
      • Advanced topics in multiprocessor environments and thread synchronization
      • c++0x (C++11) new features reviews

 

About Threads
The concept of processes and threads is a familiar crossing to all of you. Here, I just want to remind you of some important concepts:
  1. A process can contain multiple threads at the same time.
     
  2. We generally think of threads as the smallest concurrency and dispatch unit that the operating system can recognize (don't tell me there's Green Thread or Fiber,os Kernel don't know or participate in scheduling of these objects).
     
  3. Multiple threads in the same process share code snippets (code and constants), data segments (static and global variables), and extension segments (heap storage), but each thread has its own stack segment . The stack segment is also called the runtime stack, which holds all local variables and temporary variables (parameters, return values, temporary constructed variables, etc.). This article is very important for some of the concepts below. Note, however, that the various "segments" mentioned here are logically stated, and some hardware architectures or operating systems may not use segment storage physically. It doesn't matter, however, that the compiler will ensure that these logical concepts and assumptions are always set up for every C + + programmer.
     
  4. Because a thread cannot have its own "static" or "global" variable because it shares all of the memory address segments except for the stack, the operating system typically provides a mechanism called TLS(thread local Storage, which is "threads locally stored"), to compensate for this shortcoming. A similar function can be achieved through this mechanism. TLS is typically an array of pointers pointed to by a pointer in the thread control block (TCB), each element in the array is called a slot (slot), and the pointer in each slot is defined by the consumer and can point to any location (but usually to an offset in the heap store).

 

Call and return of functions
then we review the next preparatory knowledge: How the compiler implements function invocation and return. In general, the compiler creates a stack frame for each function in the current call stack. The stack frame is tasked with the following important tasks:
  1. passing parameters: Typically, the function's invocation parameters are always at the top of the stack frame. The
  2. passes the return address: tells the callee where the return statement should return, usually pointing to the next statement in the function call (the offset in the code snippet). The
  3. holds the caller's current stack pointer: facilitates the cleanup of all local variables of the callee and restores the caller's scene.
  4. holds all local variables within the current function: remember? I just said that all local and temporary variables are stored on the stack.

Review One last point: the stack is a "LIFO" data structure, but in practice most implementations of the stack support random access.

Let's look at a concrete example:

Assumes Funca, FUNCB, and FUNCC three functions, each of which receives two shaped values as its arguments. Within a certain time period on a thread, Funca calls FUNCB, and FUNCB calls FUNCC. Their stack frame should look like this:


Figure 1 function Call stack frame example

calling convention usually refers to the order in which the caller presses arguments into the stack (or into registers), and who (the caller or callee) cleans up these parameters as they are returned.


 

Call and Return of C + + functions
Let's first clarify that the "C + + function" here refers to:
    1. The function may throw an exception directly or indirectly: the definition of the function is stored in a C + + compilation (rather than a traditional C) cell, and the function does not use a throw () exception filter.
    2. Or a try block is used within the definition of the function.

Both of these meet one of them. In order to be able to catch exceptions successfully and to complete stack fallback correctly (stack unwind), the compiler must introduce some additional data structures and corresponding processing mechanisms. Let's start by looking at what the stack frame, which introduces the exception handling mechanism, might look like:


Figure 2 C + + function call Stack Framework Example

As you can see from Figure 2, there is something more in the stack frame of each C + + function. If you look closely, you will find that the extra thing is exactly an EXP type structure. Further analysis will reveal that this is a typical unidirectional-linked-list structure:

    • The Piprev member points to the previous node of the list, which is used primarily to search for matching catch blocks in the function call stack, and to complete the stack fallback work.

    • The Pihandler member points to the data structures necessary to complete the exception capture and stack fallback (mainly two tables with key data: "Try" block table:tbltryblocks and "stack fallback table":tblunwind).

    • The Nstep member is used to locate the try block and to find the correct entry in the stack fallback table.

It is necessary to note that the compiler defines a EHDL structure for each C + + function, but only the tbltryblocks member is defined for the function that contains the "try" block . In addition, the exception handler maintains a pointer to the current exception-handling framework for each thread. The pointer points to the end of the chain of the exception handler, usually in a TLS slot or in a similar role.

Finally, take a look at Figure 2 again and leave at least a general impression of the data structure. We'll discuss them in more detail in later sections.

Note : For the sake of simplicity, most of the data structures described in this article omit some members that are irrelevant to the topic.

 

Stack fallback (stack unwind) mechanism
"Stack fallback" is a new concept introduced in C + + with exception handling, primarily to ensure that after an exception is thrown, captured, and processed, all objects whose lifetimes have ended are properly refactored and the space they occupy is properly reclaimed.

Benefit from the introduction of the stack fallback mechanism, and the C + + class supported by the "resource request is initialized" semantics, so that we can finally completely farewell to the neither elegant nor secure setjmp/longjmp call, simple and safe to achieve remote jump. I think this is also the exception handling mechanism of C + + is the only reasonable way to apply the error.

Let's take a look at how the compiler implements the stack fallback mechanism:


Figure 3 C + + stack fallback mechanism

In the "Funcunwind" function in Figure 3, all real code is marked in black and blue, and the code generated by the compiler is indicated by the gray and orange fonts. In this case, the Nstep variables and Tblunwind members given in Figure 2 are very obvious.

Nstep variables are used to track the construction and destruction stages of local objects within a function. With the Tblunwind table generated by the compiler for each function, the fallback mechanism can be completed. The pfndestroyer field in the table records the destructor (destructor pointer) that should be performed at the corresponding stage, and thepObj field records the object this pointer offset that corresponds to it. By adding the offset value referred to by POBJ to the current stack frame base (EBP), it is necessary to substitute the this pointer of the Pfndestroyer destructor to complete the destruction of the object. The nnextidx field points to the next row (subscript) where the destructor is needed.

In the event of an exception, the exception handler first checks the nstep value within the framework of the current function stack and obtains the tblunwind[] table through pihandler . It then takes Nstep as a subscript into the table, executes the destructor for the row definition, and then turns to the next line that is pointed to by Nnextidx until Nnextidx is 1. After the end of the stack fallback of the current function, the exception handler can repeat the above operation along the Piprev value in the current function stack frame back to the previous node in the exception processing chain until all the fallback work is complete.

It is worth mentioning that the value of the Nstep is fully determined at compile time, and the runtime only needs to perform several simple integer assignments (usually directly assigned to a register in the CPU). In addition, for all internal types and types that use the default construct, the Destructor method (and all of its members and base classes also use the default method), the value of nstep is not affected by its creation and destruction.

Note : If, in the process of a stack fallback, the exception is thrown again due to a call to the destructor (exception in the exception), it is considered a serious failure of the exception handling mechanism. At this point the process will be forcibly banned. To prevent this, use the "std::uncaught_exception ()" method in all destructors that may throw exceptions to determine whether a stack fallback is currently in progress (that is, there is an uncaught or incomplete exception). If so, then the exception should be suppressed for re-throwing.

 

Exception trapping mechanism
When an exception is thrown, a C + + exception capture mechanism is immediately thrown:


Figure 4 C + + exception trapping mechanism

In the previous section, we have seen the role of the nstep variable in tracking object construction and destruction. In fact, in addition to being able to track the object creation and destruction phases, Nstep is able to identify whether the current execution point is in a try block, and in which try block (if the current function has more than one try block). This is done by assigning a unique ID value to each of the entry and exit Nstep of each try block, and ensuring that the nstep changes within the corresponding try block are within this range.

In implementing exception trapping, first, the C + + exception handler checks to see if the location where the exception occurred is within a try block of the current function. This work can be done by using the Nstep value of the current function in the entry in the Pihandler point to tbltryblocks[] table in the range of [Nbeginstep, Nendstep].

For example: If the FUNCB in Figure 4 has an exception in nstep = = 2 O'Clock, 2∈[1, 3 is found by comparison to the tbltryblocks[] table in the FUNCB, so the exception occurs in the first try block within the FUNCB.

Second, if the exception occurs in a try block in the current function, try to match the tblcatchblocks[] table in the corresponding entry for that tbltryblocks[] . The tblcatchblocks[] table records information about all catch blocks that are associated with the specified try block, including information such as the type of exception that the catch block can catch and its start address.

If a matching catch block is found, the current exception object is copied to this catch block and then jumps to its entry address to execute the in-block code.

Otherwise, the occurrence of the exception is not within the try block of the current function, or there is no catch block in the try block that matches the current exception, and the above process is repeated over the Piprev address in the function stack frame (that is, the previous node in the exception handling chain). Until a matching catch block is found or the first node of the exception handling chain is reached. For the latter, we call an uncaught exception, and for C + + exception handlers, an uncaught exception is a critical error that will cause the current process to be forced to end.

Note : Although the tbltryblocks[in the example in Figure 4] has only one entry, tblcatchblocks[in this entry is only a single line. However, in practice, multiple records are allowed in both tables. This means that there can be multiple try blocks in a function, and each try block can follow multiple catch blocks that are matched to it.

Note : In the standard sense, the stack fallback at the time of exception is accompanied by an exception-capturing process that moves up and down the exception-handling chain. However, some compilers do a one-time stack fallback after the exception capture has been completed earlier. Regardless of the approach used, unless you are developing an embedded application in which memory is tightly constrained, it is common for us to understand the standard semantics without creating any problems.

Note : There are actually some more critical fields in tblcatchblocks that are deliberately omitted. For example, indicate the field of the Catch block exception object Replication (transfer value (copy construct) or address (reference or pointer)), and where to store the copied exception object (relative to the entry address offset position) and other information.

Exception throws
Next, we discuss the last link in the whole C + + exception handling mechanism, throwing the exception:


Figure 5 C + + exception throws

When compiling a piece of C + + code, the compiler replaces all throw statements with a specified function in its C + + runtime library, where we call it __cxxrtthrowexp(as with all other data structures and property names mentioned in this article, It can be any name in the actual application). The function receives a compiler-approved internal structure (we call it the EXCEPTION structure). This structure contains the starting address of the exception object to be thrown, the destructor used to destroy it, and its type_info information. For exception class hierarchies that do not have the RTTI mechanism enabled (the compiler disables the RTTI mechanism or the virtual table is not used in the class hierarchy), you may also want to include type_info information for all of its base classes in order to match the corresponding Catch block.

In the dark gray block diagram in Figure 5, we use C + + pseudocode to show the "Throw Myexp (1)" In the function Funca, and the statement will eventually be translated by the compiler. In fact, in most cases, the__cxxrtthrowexp function is the "exception handler" that we have mentioned many times before, and the important work of exception capture and stack fallback is done by it.

__cxxrtthrowexp First receives (and saves) theEXCEPTION object, and then finds the Pihandler, Nstep, corresponding to the current function from the tls:current exphdl . and other exception handling related data, and follow the mechanism described above to complete the exception capture and stack fallback. This completes the whole set of exception handling mechanisms, including "throw", "capture", "fallback" and so on.

 

Structured exception handling in Windows
Microsoft Windows has a mechanism called structured exception handling, a very well-known "Memory Access violation" error dialog that is a manifestation of this mechanism. Windows Structured exception handling is surprisingly similar to the C + + exception handling mechanism discussed earlier, and is also implemented using a similar chained structure. For applications under Windows, simply register the exception processor using the SetUnhandledExceptionFilter API, and use fs:[0] instead of the tls:current EXPHDL, as described earlier, to combine these two error handling mechanisms For one. The advantages of this are obvious:
  • The implementation of the C + + exception handler is simplified because of the mechanism provided directly by the operating system.
  • Enables the catch (...) block to capture exceptions generated by the operating system (for example, "Memory access violation", and so on).
  • Enables the operating system's exception handling mechanism to capture all C + + exceptions.

In fact, most of the exception mechanisms for C + + compilers under Windows are implemented in this way.

Overhead analysis of exception handling mechanism
At this point, we have fully elaborated the complete set of C + + exception handling mechanism implementation principle. As I mentioned at the beginning of this article, as a C + + programmer, Understanding the implementation of one of its features is primarily to avoid the erroneous use of this feature. To achieve this, some additional overhead analysis is done on the basis of understanding the implementation principle:
Characteristics Time Overhead Space overhead
Ehdl No run-time overhead each "C + + function" is a Ehdl object, where the tbltryblocks[] member is used only when the function contains at least one try block. Typically less than 64 bytes.

 

C + + Stack Framework Extremely high O (1) efficiency, with 3 additional shaping assignments and one TLS access per call. Two pointers per call and one shaping overhead. Typically less than 16 bytes.

 

Step tracking Very high O (1) efficiency every time in and out of the try block or object construction/destruction of an immediate number assignment. None (the corresponding item in the C + + stack Framework is already entered).

 

Exception throwing, capturing, and stack fallback The exception throw is an O (1) level operation. Capturing and stack fallback in a single function is also an O (1) operation.

However, the overall cost of an exception capture is O (m), where m equals the number of functions in the current function call stack, from the point at which the exception was thrown to the function call passed between the matching catch block, including the try block (that is, a valid tbltryblocks[] is defined).

The total cost of the stack fallback is O (n), where n equals the number of function calls that pass through the matching catch block from the location where the exception was thrown in the current function call stack.

Before the end of exception processing, you need to save the exception object and its destructor pointers and the corresponding type_info information.

Depending on the size of the object, compiler options (whether open RTTI) and the parameters of the exception trap (transmit value or address) and other factors have a large change. Typically less than 256 bytes.

 

It can be seen that the exception handling mechanism of C + + is very effective when no exception is thrown. After an exception is thrown, it is possible to perform several shaping comparisons (try block table matching) operations in the context of the current function call stack, but this usually does not exceed dozens of times. For most CPUs 15 years ago, the shaping comparison was only about 1: cycles, so the efficiency of exception trapping was still high. The efficiency of the stack fallback is basically equivalent to the return statement.

Consider that even traditional function calls, error handling, and stepwise return mechanisms are not without cost. These costs are still acceptable in the vast majority of cases. In terms of space overhead, the introduction of a ehdl struct per "C + + function" can significantly increase the target file size and memory overhead in some extreme cases. But in typical cases, they have little effect, but they are not too small to be completely ignored. If you are developing an application for a resource-constrained environment, you may want to consider turning off exception handling and RTTI mechanisms to conserve storage space.

The above discussion is a typical implementation of the anomaly mechanism, the specific compiler vendors may have their own optimization and improvement Program, but the overall access will not be very large.

Section
Exception handling is one of the most useful new features in C + +. In most cases, they have excellent performance and satisfactory space-time efficiency. Exception handling is essentially another return mechanism. However, in terms of software engineering, module design, coding habits and space-time efficiency, in addition to being able to replace the traditional setjmp/longjmp function occasionally in the premise of sufficient documentation, it should be guaranteed to be used only in the error handling mechanism of the program.

In addition, the use of long jumps is both error-prone and difficult to understand and maintain. The coding process should also be avoided as far as possible. For general instructions on how to use exceptions, refer to: Code style vs. layout: exceptions.

Http://baiy.cn/doc/cpp/inside_exception.htm

C + + exception mechanism implementation and overhead analysis (large graph, the compiler will add EHDL structure for each function, form a one-way list, very famous "Memory Access violation" error dialog box is a manifestation of this mechanism)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.