【问题标题】:Is __finally supposed to run after EXCEPTION_CONTINUE_SEARCH?__finally 应该在 EXCEPTION_CONTINUE_SEARCH 之后运行吗?
【发布时间】:2015-09-27 08:11:03
【问题描述】:

在以下代码中,函数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 在实际输出中明显缺失。这是一个错误还是我忽略了一些细节?

为了完整起见,使用 VS2015,为 x64 编译。令人惊讶的是它不会在 x86 上发生,这让我相信这确实是一个错误。

【问题讨论】:

  • 嗯,不好。这不是一个新问题,VS2013 的行为方式相同。看起来像 /SAFESEH 对我的结构限制,特定于递归,它在非递归情况下工作正常。很怀疑这里的任何人都可以解决这个问题,最好ping connect.microsoft.com。
  • @IInspectable 是编译器而不是平台使这个定义很好。
  • @IInspectable 不,是编译器决定发出代码。它可以发出代码来对 null 取消引用做一些不同的事情。没有编译器。
  • @DavidHeffernan:编译器不会发出任何特殊的东西。 *(int*)0=0; 产生指令 mov dword ptr [0],0。 SEH 是一种系统服务,操作系统可以很好地定义结果。
  • @IInspectable 除非编译器决定在取消引用 nullptr 时发出代码以执行不同的操作

标签: c++ winapi seh structured-exception


【解决方案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

这是下一个函数中的错误

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

这个函数的旧实现有错误here

                    //
                    // 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-&gt;ExceptionFlags) 来修复这个错误

__C_specific_handler在不同的crt库中实现(在某些情况下使用静态链接,在某些情况下将从vcruntime*.dllmsvcrt.dll导入(被转发到ntdll.dll))。也ntdll.dll 导出这个函数 - 但是在最新的win10版本(14393)中它仍然没有修复

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-19
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    • 2015-08-10
    • 1970-01-01
    • 2018-06-13
    相关资源
    最近更新 更多