“__finally”语句块是否应在“EXCEPTION_CONTINUE_SEARCH”后运行?

8
在下面的代码中,函数foo递归调用了一次。内部调用导致引发访问冲突异常。外部调用捕获了该异常。
#include <windows.h>
#include <stdio.h>

void foo(int cont)
{
    __try
    {
        __try
        {
            __try
            {
                if (!cont)
                    *(int *)0 = 0;
                foo(cont - 1);
            }
            __finally
            {
                printf("inner finally %d\n", cont);
            }
        }
        __except (!cont? EXCEPTION_CONTINUE_SEARCH: EXCEPTION_EXECUTE_HANDLER)
        {
            printf("except %d\n", cont);
        }
    }
    __finally
    {
        printf("outer finally %d\n", cont);
    }
}

int main()
{
    __try
    {
        foo(1);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        printf("main\n");
    }
    return 0;
}

这里的期望输出应该是:
inner finally 0
outer finally 0
inner finally 1
except 1
outer finally 1

但是,真实输出中明显缺少outer finally 0。这是一个bug还是我忽略了一些细节?

为了完整起见,使用VS2015编译x64时会发生这种情况。令人惊讶的是,它在x86上不会发生,这导致我认为这真的是一个bug。


1
不太好。这不是一个新问题,VS2013的行为方式也是如此。在我看来,这似乎是/SAFESEH的结构限制,特别是递归方面,在非递归情况下它可以正常工作。这里没有人能够解决这个问题,最好向connect.microsoft.com反馈。 - Hans Passant
1
@IInspectable 这是编译器而不是平台使其定义良好。 - David Heffernan
2
@IInspectable 不会。是编译器决定是否发出代码。它可以发出代码以在空指针引用时执行不同的操作。但没有编译器这样做。 - David Heffernan
2
除非编译器决定在取消引用 nullptr 时发出执行不同操作的代码,否则 @IInspectable。 - David Heffernan
1
伙计们,没关系,如果我使用RaiseException抛出异常,行为是完全一样的。 - avakar
显示剩余11条评论
1个回答

1

存在并且更简单的示例(我们可以删除内部的try/finally块:

void foo(int cont)
{
    __try
    {
        __try
        {
            if (!cont) *(int *)0 = 0;
            foo(cont - 1);
        }
        __except (cont? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
        {
            printf("except %d\n", cont);
        }
    }
    __finally
    {
        printf("finally %d\n", cont);
    }
}

带有输出

except 1
finally 1

所以finally 0块没有执行。但在非递归情况下 - 没有错误:

__try
{
    foo(0);
} 
__except(EXCEPTION_EXECUTE_HANDLER)
{
    printf("except\n");
}

输出:

finally 0
except

这是next函数中的错误

EXCEPTION_DISPOSITION
__C_specific_handler (
    _In_ PEXCEPTION_RECORD ExceptionRecord,
    _In_ PVOID EstablisherFrame,
    _Inout_ PCONTEXT ContextRecord,
    _Inout_ PDISPATCHER_CONTEXT DispatcherContext
    );

这个函数的旧实现有一个错误在这里:

                    //
                    // try/except - exception filter (JumpTarget != 0).
                    // After the exception filter is called, the exception
                    // handler clause is executed by the call to unwind
                    // above. Having reached this point in the scan of the
                    // scope tables, any other termination handlers will
                    // be outside the scope of the try/except.
                    //

                    if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget) { // bug
                        break;
                    }

如果我们已经安装了最新的VC编译器/库,请搜索chandler.c(在我的安装中位于\VC\crt\src\amd64\chandler.c
现在可以查看文件中的下一个代码:
                if (TargetPc == ScopeTable->ScopeRecord[Index].JumpTarget
                    // Terminate only when we are at the Target frame;
                    // otherwise, continue search for outer finally:
                    && IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)
                    ) {
                    break;
                }

所以,为了修复这个错误,添加了一个额外的条件 IS_TARGET_UNWIND(ExceptionRecord->ExceptionFlags)

__C_specific_handler 在不同的crt库中实现(在某些情况下与静态链接一起使用,在某些情况下将从 vcruntime*.dllmsvcrt.dll 导入(被转发到 ntdll.dll))。此外,ntdll.dll 导出此函数 - 然而,在最新的win10版本(14393)中仍未修复该问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接