编译器使用两种方法的混合。 MSVC使用内联析构函数调用正常的代码流,并以相反的顺序清理代码块以用于早期返回和异常。 在正常流程中,它使用单个隐藏的本地整数来跟踪到目前为止的构造函数进度,因此它知道在早期返回时跳转到何处。 单个整数就足够了,因为范围总是形成一个树(而不是说为已经成功构建或未成功构建的每个类使用位掩码)。 例如,以下相当短的代码使用具有非平凡析构函数的类和一些随机返回在整个...
...
if (randomBool()) return;
Foo a;
if (randomBool()) return;
Foo b;
if (randomBool()) return;
{
Foo c;
if (randomBool()) return;
}
{
Foo d;
if (randomBool()) return;
}
...
...可以在x86上扩展为如下所示的伪代码,其中构造函数进度在每个构造函数调用之后立即递增(有时通过多于一个到下一个唯一值)并在每个构造函数紧接之前递减(或“弹出”到更早的值)析构函数。 请注意,具有普通析构函数的类不会影响此值。
...
save previous exception handler // for x86, not 64-bit table based handling
preallocate stack space for locals
set new exception handler address to ExceptionCleanup
set constructor progress = 0
if randomBool(), goto Cleanup0
Foo a;
set constructor progress = 1 // Advance 1
if randomBool(), goto Cleanup1
Foo b;
set constructor progress = 2 // And once more
if randomBool(), goto Cleanup2
{
Foo c;
set constructor progress = 3
if randomBool(), goto Cleanup3
set constructor progress = 2 // Pop to 2 again
c.~Foo();
}
{
Foo d;
set constructor progress = 4 // Increment 2 to 4, not 3 again
if randomBool(), goto Cleanup4
set constructor progress = 2 // Pop to 2 again
d.~Foo();
}
// alternate Cleanup2
set constructor progress = 1
b.~Foo();
// alternate Cleanup1
set constructor progress = 0
a.~Foo();
Cleanup0:
restore previous exception handler
wipe stack space for locals
return;
ExceptionCleanup:
switch (constructor progress)
{
case 0: goto Cleanup0; // nothing to destroy
case 1: goto Cleanup1;
case 2: goto Cleanup2;
case 3: goto Cleanup3;
case 4: goto Cleanup4;
}
// admitting ignorance here, as I don't know how the exception
// is propagated upward, and whether the exact same cleanup
// blocks are shared for both early returns and exceptions.
Cleanup4:
set constructor progress = 2
d.~Foo();
goto Cleanup2;
Cleanup3:
set constructor progress = 2
c.~Foo();
// fall through to Cleanup2;
Cleanup2:
set constructor progress = 1
b.~Foo();
Cleanup1:
set constructor progress = 0
a.~Foo();
goto Cleanup0;
// or it may instead return directly here
编译器当然可以重新排列这些块,无论如何它认为更有效,而不是将所有清理结束。 早期的返回可能会跳转到函数末尾的备用Cleanup1 / 2。 在64位MSVC代码上,异常通过表来处理,这些表将异常发生的指令指针映射到相应的代码清理块。