是否可以从 std::abort 中恢复?

Is it possible to recover from std::abort?

我们的 C 代码库使用 assert 检查是否满足 preconditions/post 条件。

#include <cstdlib>
#include <cassert>

void Aborting_Function(int n);

int main(){

    Aborting_Function(1); //good
    Aborting_Function(0); //calls std::abort()

    //Is it possible to recover somehow?
    //And continue on...
}

void Aborting_Function(int n){
    assert(n > 0);
    //impl...
}

在单元测试中,我想验证函数是否正确遵循它们的约定
(在他们应该的时候中止)。

是否可以从 std::abort 恢复?

我意识到让单元测试检查断言应该检查的完全相同的东西似乎有点重复,但这会很有帮助,因为我们可以自动检查不应该工作的特定用例。

Is it possible to recover from std::abort?

这取决于您所说的恢复是什么意思。来自 http://en.cppreference.com/w/cpp/utility/program/abort

Causes abnormal program termination unless SIGABRT is being caught by a signal handler passed to signal and the handler does not return.

如果您希望能够从调用 std::abort 的地方继续,那么答案是 。如果你希望能够在程序退出之前做一些事情,那么答案是

我猜您希望能够从调用 std::abort 的地方继续。因此,对于您的用例,答案是

根据POSIX,

The abort() function shall cause abnormal process termination to occur, unless the signal SIGABRT is being caught and the signal handler does not return.

这意味着如果您捕捉到信号和信号处理程序 returns,仍然需要终止程序(例如,通过将信号处理程序重置为默认终止行为,然后提高再次发出信号)。

所以 "recover" 的唯一方法是从信号处理程序中捕获信号而不是 return。所以无法访问 Aborting_Function(0) 之后的行。此外,您可能不希望您的程序在信号处理程序中度过余生,因为这样所有外部变量都变得无法安全访问(无锁原子除外)。不太好看

在 Windows 上?我不知道。

简答,"no"。

与其颠覆 abort(),不如考虑使用 google 测试框架。

这有 DEATH_TEST(此处的文档:https://github.com/google/googletest/blob/master/googletest/docs/V1_7_AdvancedGuide.md

本质上,它所做的是分叉一个子进程并检查该语句是否导致它退出(如果它中止,它会这样做)。

对于单元测试,可以从那里替换 abort() 和 longjmp(),或者当通过 C++ 测试时从那里抛出。

例如(表示为C++):

#include <cassert>
#include <csetjmp>
#include <stdexcept>
#include <iostream>

#ifndef lest_ABORT_SIGNATURE
# if _MSC_VER
#  define lest_NORETURN  __declspec(noreturn)
#  define lest_ABORT_SIGNATURE()  _ACRTIMP lest_NORETURN void __cdecl abort(void)
# else
#  define lest_NORETURN  [[noreturn]]
#  define lest_ABORT_SIGNATURE()  lest_NORETURN void __cdecl abort()
# endif
#else
# ifndef  lest_NORETURN
#  define lest_NORETURN
# endif
#endif

#if USE_LONGJMP
    jmp_buf env;

    lest_ABORT_SIGNATURE()
    {
        std::longjmp( env, 1 );
    }
#else
    struct Abort{};

    lest_NORETURN void my_abort()
    {
        throw Abort{};
    }

    lest_ABORT_SIGNATURE()
    {
        // throw indirectly and prevent warning in VC14:
        my_abort();
    }
#endif

int main()
{
#if USE_LONGJMP
    if ( ! setjmp( env ) )
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    else
    {
        std::cout << "Intercepted abort\n";
    }
#else
    try
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    catch ( Abort const & )
    {
        std::cout << "Caught Abort\n";
    }
    catch ( std::exception const & e )
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
#endif
    std::cout << "End\n";
}

#if 0
cl  -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe
g++ -Wall -DUSE_LONGJMP=1 -std=c++11 -o abort-own.exe abort-own.cpp && abort-own.exe
#endif

使用 VC14 (VS2015) 编译并运行:

cl -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe

产生以下输出:

...
assert(false):
Assertion failed: false, file abort-own.cpp, line 45
Intercepted abort
End

使用 VC14 之前的编译器编译会产生 link 错误:

LIBCMT.lib(abort.obj) : error LNK2005: _abort already defined in {file}
{exe} : fatal error LNK1169: one or more multiply defined symbols found

这可以通过以下方式治愈:

  • 编译时包括/link /FORCE:MULTIPLE
  • creating a runtime library with abort() removed.

使用 -std=c++03-std=c++11 通过 g++ 编译不会导致多重定义的符号。

我正在为 lest test framework:

开发以上代码的更详细版本

有办法做到这一点,但有点 non-conformant。

您可以使用 HippoMocks(免责声明:我是作者)来模拟该函数并使其抛出异常,然后您可以使用它来检查您的测试框架。您不能使它 return 成为一个值,因为它被标记为 noreturn 并且编译器不会生成任何代码来处理它的 returns。

EXPECT_CALL(&abort).Throw(42);

请注意,这至少违反了 C 和 C++ 中的 5 条不同规则,所以更大的问题是,你应该这样做吗?据我所知,您使用断言来防止显示内部不一致的事情。这些是你不应该测试的东西。无论有没有断言,你的所有程序都应该同样有效。如果您希望从您的代码中测试任何类型的行为,那么那永远不应该是断言,因为它不是意外状态(哎呀,您正在测试该状态,所以它是一个 well-tested那个)。

所以你可以,但你不应该想要。