现在的位置: 首页 > 综合 > 正文

Chapter 23: Termination Handlers(2)Understanding Termination Handlers by Example(1)

2018年04月10日 ⁄ 综合 ⁄ 共 6833字 ⁄ 字号 评论关闭

Because the compiler and the operating system are intimately involved with the execution of your code when you use SEH, I believe that the best way to demonstrate how SEH works is by examining source code samples and discussing the order
in which the statements execute in each example.

Therefore, the next few sections show different source code fragments, and the text associated with each fragment explains how the compiler and operating system alter the execution order of your code.

因为在你使用SEH的时候,编译器和OS与你的代码执行密切相关,我认为展示SEH如何运作的最好的办法是分析例子中的执行顺序。
因此,接下来的几个小节会展示不同的代码并解释编译器和OS是如何修改你的代码执行顺序。

Funcenstein1

To appreciate the ramifications of using termination handlers, let's examine a more concrete coding example:

DWORD Funcenstein1() {
   DWORD dwTemp;

   // 1. Do any processing here.
   ...
   __try {
      // 2. Request permission to access
      //    protected data, and then use it.
      WaitForSingleObject(g_hSem, INFINITE);

      g_dwProtectedData = 5;
      dwTemp = g_dwProtectedData;
   }
   __finally {
      // 3. Allow others to use protected data.
      ReleaseSemaphore(g_hSem, 1, NULL);
   }

   // 4. Continue processing.
   return(dwTemp);
}

The numbered comments in the preceding code sample indicate the order in which your code will execute. In Funcenstein1, using the try-finally
blocks isn't doing much for you. The code will wait for a semaphore, alter the contents of the protected data, save the new value in the local variable dwTemp, release the semaphore,
and return the new value to the caller.

上述例子中的数字标识了代码执行的顺序。在这个例子里,try、finally没有太大的帮助。这段代码会等待一个semaphore变为有信号,之后修改被保护的数据并保存在临时变量dwTemp中,释放semaphore,最后返回dwTemp。

Funcenstein2

Now let's modify the function a little and see what happens:

DWORD Funcenstein2() {
   DWORD dwTemp;

   // 1. Do any processing here.
   ...
   __try {
      // 2. Request permission to access
      //    protected data, and then use it.
      WaitForSingleObject(g_hSem, INFINITE);

      g_dwProtectedData = 5;
      dwTemp = g_dwProtectedData;

      // Return the new value.
      return(dwTemp);
   }
   __finally {
      // 3. Allow others to use protected data.
      ReleaseSemaphore(g_hSem, 1, NULL);
   }

   // Continue processing--this code
   // will never execute in this version.
   dwTemp = 9;
   return(dwTemp);
}

In Funcenstein2, a return statement has been added to the end of the try
block. This return statement tells the compiler that you want to exit the function and return the contents of the dwTemp
variable, which now contains the value 5. However, if this return statement had been executed, the thread
would not have released the semaphore—and no other thread would ever regain control of the semaphore. As you can imagine, this kind of sequence can become a really big problem because threads waiting for the semaphore might never resume execution.

在本例中,try部分的末尾增加了一个return。这个return告诉编译器,你想从函数中退出并返回临时变量(值为5)。然而,如果这个语句被执行,这个线程将不会释放semaphore,这意味着,其他线程再也无法获得这个semaphore的控制权。你可以设想一下,这样做会造成严重的死锁。

However, by using the termination handler, you have avoided the premature execution of the return statement. When the return
statement attempts to exit the try block, the compiler makes sure that the code in the finally block
executes first. The code inside the finally block is guaranteed to execute before the return statement
in the try block is allowed to exit. In Funcenstein2, putting the call to ReleaseSemaphore
into a termination handler block ensures that the semaphore will always be released. There is no chance for a thread to accidentally retain ownership of the semaphore, which would mean that all other threads waiting for the semaphore would never be scheduled
CPU time.

然后,借助termination hander,你可以避免这种过早的return。当return试图退出try代码块,编译器会确保finally中的代码先被执行。

After the code in the
finally
block executes, the function does, in fact, return. Any code appearing below the finally block doesn't execute because the function returns
in the try block. Therefore, this function returns the value 5, not the value 9.

在finally中的代码执行后,这个函数返回了。finally下的代码没有被执行,因为函数在try代码块里return了。因此,这个函数返回5,而不是9.

You might be asking yourself how the compiler guarantees that the finally block executes before the try
block can be exited. When the compiler examines your source code, it sees that you have coded a return statement inside a try
block. Having seen this, the compiler generates code to save the return value (5 in our example) in a temporary variable created by the compiler. The compiler then generates code
to execute the instructions contained inside the finally block; this is called alocal unwind. More specifically, a local unwind
occurs when the system executes the contents of a finally block because of the premature exit of code in a try
block. After the instructions inside the finally block execute, the value in the compiler's temporary variable is retrieved and returned from the function.

你也许会问,编译器是怎么保证finally中的代码在try退出前被执行。当编译器检视你的代码,发现在try的尾部有一个return的时候,它会创建一个临时变量保存值5。然后产生执行finally中代码的指令,这被称为a local unwind。

As you can see, the compiler must generate additional code and the system must perform additional work to pull this whole thing off. On different CPUs, the steps needed for termination handling to work vary. You should avoid writing code that
causes premature exits from the try block of a termination handler because the performance of your application could be adversely impacted. Later in this chapter, I'll discuss the__leave
keyword, which can help you avoid writing code that forces local unwinds.

你可以看到,为了搞定整件事,编译器必须产生额外的代码,同时系统必须执行额外的操作。在不同的CPU上,执行termination handing的步骤有所不同。你应该避免这种在try中提前退出,因为这样性能会受到一定影响。在本章稍后,我会谈一下关键字_leave,它可以帮助你避免写出会造成local unwinds的代码。

Exception handling is designed to capture exceptions—the exceptions to the rule that you expect to happen infrequently (in our example, the premature return). If a
situation is the norm, checking for the situation explicitly is much more efficient than relying on the SEH capabilities of the operating system and your compiler to trap common occurrences.

Exception handing的设计初衷是捕获异常。异常是你希望很少出现的情况。如果某个情况是常态,那么显式检查该情况要比使用SEH有效得多。

Note that when the flow of control naturally leaves the
try
block and enters the
finally
block (as shown in
Funcenstein1
), the overhead of entering the
finally
block is minimal. On
x
86 CPUs using Microsoft's compiler, a single machine instruction is executed as execution leaves the try block to enter the finally
block—I doubt that you will even notice this overhead in your application. When the compiler has to generate additional code and the system has to perform additional work, as inFuncenstein2,
the overhead is much more noticeable.

注意,当程序自然地离开try,进入finally时,进入finally的overhead是最小的。如果是x86 CPU,使用微软的编译器,离开try进入finally只需要一条指令,你可能根本注意不到这其中的overhead。但是如果是本例的情况,overhead会明显得多。

Funcenstein3

Now let's modify the function again and take a look at what happens

DWORD Funcenstein3() {
   DWORD dwTemp;

   // 1. Do any processing here.
   ...
   __try {
      // 2. Request permission to access
      //    protected data, and then use it.
      WaitForSingleObject(g_hSem, INFINITE);

      g_dwProtectedData = 5;
      dwTemp = g_dwProtectedData;

      // Try to jump over the finally block.
      goto ReturnValue;
   }

   __finally {
      // 3. Allow others to use protected data.
      ReleaseSemaphore(g_hSem, 1, NULL);
   }

   dwTemp = 9;
   // 4. Continue processing.
   ReturnValue:
   return(dwTemp);
}

In Funcenstein3, when the compiler sees the
goto statement in the
try block, it generates a local unwind to execute the contents of the
finally block first. However, this time, after the code in the
finally block executes, the code after the
ReturnValue label is executed because no return occurs in either the
try or
finally block. This code causes the function to return a
5. Again, because you have interrupted the natural flow of control from the
try block into the
finally block, you could incur a high performance penalty depending on the CPU your application is running on.

在本例中当编译器在try中看到goto,它产生了一个local unwind来先执行finally里的内容。然后,这次,在finally中的代码被执行后,标签ReturnValue后的代码被执行,因为try和finally里都没有return。最终该函数返回5.同时,由于打乱了自然流程,你会付出高昂的性能开销。

抱歉!评论已关闭.