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

Is it possible to recover from std::abort?

本文关键字:abort 恢复 std 是否      更新时间:2023-10-16

我们的C代码库使用assert来检查是否满足先决条件/后置条件。

#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中恢复

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

简短回答,"否"。

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

这是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 abortn";
    }
#else
    try
    {
        std::cout << "assert(false):n";
        assert( false );
    }
    catch ( Abort const & )
    {
        std::cout << "Caught Abortn";
    }
    catch ( std::exception const & e )
    {
        std::cout << "Exception: " << e.what() << "n";
    }
#endif
    std::cout << "Endn";
}
#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之前的编译器进行编译会产生链接错误:

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

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

  • 包括编译时的CCD_ 1
  • 创建删除了CCD_ 2的运行库

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

我正在为最新的测试框架开发上面代码的更详细版本

  • 替换abort()、longjmp()
  • 替换中止(),抛出

根据POSIX,

abort()函数将导致异常进程终止,除非信号SIGABRT被捕获并且信号处理程序没有返回。

这意味着,如果您捕捉到信号并且信号处理程序返回,则仍然需要终止程序(例如,通过将信号处理程序重置为默认终止行为,然后再次引发信号)。

因此,"恢复"的唯一方法是捕获信号,而不是从信号处理程序返回。因此Aborting_Function(0)之后的线路无法到达。此外,您可能不希望您的程序在信号处理程序中度过余生,因为这样一来,所有外部变量都变得不安全,无法访问(除了无锁原子)。这不是很好。

不过在Windows上?我不知道。

有可能从std::abort恢复吗?

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

导致程序异常终止,除非SIGABRT被传递给信号的信号处理程序捕获,并且该处理程序不返回。

如果您希望能够从调用std::abort的位置继续,那么答案是。如果您希望能够在程序退出之前执行某些操作,那么答案是

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

有一种方法可以做到这一点,但有点不符合。

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

EXPECT_CALL(&abort).Throw(42);

请注意,这至少违反了C和C++中的5条不同规则,所以更大的问题是,你应该这样做吗?据我所知,你使用断言来防止出现内部不一致的情况。这些都是你不应该测试的东西。您的所有程序在有断言和没有断言的情况下都应该是同等有效的。如果你期望你想要测试的代码有任何类型的行为,那么这永远不应该是断言,因为这不是一个意外的状态(见鬼,你正在测试那个状态,所以它在这方面测试得很好)。

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