c++ - GCC/GCOV 为使用 throw()/noexcept 的函数生成的不同分支覆盖

标签 c++ c++11 gcc gcov lcov

想了解一下GCC编译器在使用noexcept时生成的分支或 throw()用于标记非抛出函数。我知道 noexcept 之间的区别和 throw() , 但无法弄清楚使用 throw() 时定义的附加分支是什么而不是 noexcept以及如何提高覆盖率以达到 100% 的分支机构覆盖率?

测试示例类的代码片段:

class MyClass
{
public:
    MyClass() noexcept :
        iThrowFlag(false)
    {}

    void non_throwing_method() noexcept
    {
        try
        {
            throwing_method();
        }
        catch (...)
        {
        }
    }

    void throwing_method()
    {
        if (iThrowFlag)
        {
            throw std::exception();
        }
    }

public:
    bool iThrowFlag;
};

定义了 2 个测试用例,都通过了:

  1. 验证是否non_throwing_method底层时完成 throwing_method不扔,
  2. 验证是否non_throwing_method底层时完成 throwing_method正在抛出任何异常。

下面是 GCC/GCOV 为上述代码生成的覆盖率报告,所有的行和分支都被覆盖了(覆盖了 4/4 个分支):

        -:    0:Source:myclass.hh
        -:    0:Graph:main.gcno
        -:    0:Data:main.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <iostream>
        -:    2:#include <string>
        -:    3:
        -:    4:using namespace std;
        -:    5:
        -:    6:class MyClass
        -:    7:{
        -:    8:public:
function _ZN7MyClassC2Ev called 2 returned 100% blocks executed 100%
        2:    9:    MyClass() noexcept :
        2:   10:        iThrowFlag(false)
        2:   11:    {}
        -:   12:
function _ZN7MyClass19non_throwing_methodEv called 2 returned 100% blocks executed 100%
        2:   13:    void non_throwing_method() noexcept
        -:   14:    {
        -:   15:        try
        -:   16:        {
        2:   17:            throwing_method();
call    0 returned 100%
branch  1 taken 50% (fallthrough)
branch  2 taken 50% (throw)
        -:   18:        }
        1:   19:        catch (...)
call    0 returned 100%
        -:   20:        {
        -:   21:        }
        2:   22:    }
        -:   23:
function _ZN7MyClass15throwing_methodEv called 2 returned 50% blocks executed 100%
        2:   24:    void throwing_method()
        -:   25:    {
        2:   26:        if (iThrowFlag)
branch  0 taken 50% (fallthrough)
branch  1 taken 50%
        -:   27:        {
        1:   28:            throw std::exception();
call    0 returned 100%
call    1 returned 100%
call    2 returned 0%
        -:   29:        }
        1:   30:    }
        -:   31:
        -:   32:public:
        -:   33:    bool iThrowFlag;
        -:   34:};

由于目标平台不支持 C++11 和 noexcept , 它被交换为 throw()说明符如下:

class MyClass
{
public:
    MyClass() throw() :
        iThrowFlag(false)
    {}

    void non_throwing_method() throw()
    {
        try
        {
            throwing_method();
        }
        catch (...)
        {
        }
    }

    void throwing_method()
    {
        if (iThrowFlag)
        {
            throw std::exception();
        }
    }

public:
    bool iThrowFlag;
};

在带有已检查分支的 GCOV 输出下方 (5/8)。用 throw() 编码生成额外的 4 个分支,这些分支很难理解,也很难通过测试覆盖:

        -:    0:Source:myclass.hh
        -:    0:Graph:main.gcno
        -:    0:Data:main.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <iostream>
        -:    2:#include <string>
        -:    3:
        -:    4:using namespace std;
        -:    5:
        -:    6:class MyClass
        -:    7:{
        -:    8:public:
function _ZN7MyClassC2Ev called 2 returned 100% blocks executed 100%
        2:    9:    MyClass() throw() :
        2:   10:        iThrowFlag(false)
        2:   11:    {}
        -:   12:
function _ZN7MyClass19non_throwing_methodEv called 2 returned 100% blocks executed 75%
        2:   13:    void non_throwing_method() throw()
        -:   14:    {
        -:   15:        try
        -:   16:        {
        2:   17:            throwing_method();
call    0 returned 100%
branch  1 taken 50% (fallthrough)
branch  2 taken 50% (throw)
        -:   18:        }
        1:   19:        catch (...)
call    0 returned 100%
call    1 returned 100%
branch  2 taken 100% (fallthrough)
branch  3 taken 0% (throw)
branch  4 never executed
branch  5 never executed
call    6 never executed
        -:   20:        {
        -:   21:        }
        2:   22:    }
        -:   23:
function _ZN7MyClass15throwing_methodEv called 2 returned 50% blocks executed 100%
        2:   24:    void throwing_method()
        -:   25:    {
        2:   26:        if (iThrowFlag)
branch  0 taken 50% (fallthrough)
branch  1 taken 50%
        -:   27:        {
        1:   28:            throw std::exception();
call    0 returned 100%
call    1 returned 100%
call    2 returned 0%
        -:   29:        }
        1:   30:    }
        -:   31:
        -:   32:public:
        -:   33:    bool iThrowFlag;
        -:   34:};

测试用例如下(以获取有关该用例的完整详细信息):

void test1()
{
    MyClass lObj;
    lObj.non_throwing_method();
}

void test2()
{
    MyClass lObj;
    lObj.iThrowFlag = true;
    lObj.non_throwing_method();
    lObj.iThrowFlag = false;
}

如果有人对所描述的行为有答案/解释,我将不胜感激。

编辑: 另附汇编代码(仅函数代码:`non_throwing_method1 进行比较)。

没有异常(exception):

_ZN7MyClass19non_throwing_methodEv:
.LFB1253:
    .loc 2 13 0
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA1253
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv(%rip)
    .loc 2 17 0
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
.LEHB0:
    call    _ZN7MyClass15throwing_methodEv
.LEHE0:
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip)
    .loc 2 22 0
    jmp .L7
.L6:
    movq    %rax, %rdx
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip)
    .loc 2 19 0
    movq    %rdx, %rax
    movq    %rax, %rdi
    call    __cxa_begin_catch
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip)
    call    __cxa_end_catch
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip)
.L7:
    .loc 2 22 0
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1253:
    .globl  __gxx_personality_v0
    .section    .gcc_except_table._ZN7MyClass19non_throwing_methodEv,"aG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
    .align 4
.LLSDA1253:
    .byte   0xff
    .byte   0x3
    .uleb128 .LLSDATT1253-.LLSDATTD1253
.LLSDATTD1253:
    .byte   0x1
    .uleb128 .LLSDACSE1253-.LLSDACSB1253
.LLSDACSB1253:
    .uleb128 .LEHB0-.LFB1253
    .uleb128 .LEHE0-.LEHB0
    .uleb128 .L6-.LFB1253
    .uleb128 0x1
.LLSDACSE1253:
    .byte   0x1
    .byte   0
    .align 4
    .long   0

.LLSDATT1253:
    .section    .text._ZN7MyClass19non_throwing_methodEv,"axG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
    .size   _ZN7MyClass19non_throwing_methodEv, .-_ZN7MyClass19non_throwing_methodEv
    .section    .text._ZN7MyClass15throwing_methodEv,"axG",@progbits,_ZN7MyClass15throwing_methodEv,comdat
    .align 2
    .weak   _ZN7MyClass15throwing_methodEv
    .type   _ZN7MyClass15throwing_methodEv, @function

使用 throw():

_ZN7MyClass19non_throwing_methodEv:
.LFB1253:
    .loc 2 13 0
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA1253
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv(%rip)
    .loc 2 17 0
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
.LEHB0:
    call    _ZN7MyClass15throwing_methodEv
.LEHE0:
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+8(%rip)
    .loc 2 22 0
    jmp .L3
.L8:
    movq    %rax, %rdx
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+16(%rip)
    .loc 2 19 0
    movq    %rdx, %rax
    movq    %rax, %rdi
    call    __cxa_begin_catch
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+24(%rip)
.LEHB1:
    call    __cxa_end_catch
.LEHE1:
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip), %rax
    addq    $1, %rax
    movq    %rax, __gcov0._ZN7MyClass19non_throwing_methodEv+32(%rip)
    .loc 2 22 0
    jmp .L3
.L9:
    cmpq    $-1, %rdx
    je  .L7
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+48(%rip), %rdx
    addq    $1, %rdx
    movq    %rdx, __gcov0._ZN7MyClass19non_throwing_methodEv+48(%rip)
    movq    %rax, %rdi
.LEHB2:
    call    _Unwind_Resume
.L7:
    movq    __gcov0._ZN7MyClass19non_throwing_methodEv+40(%rip), %rdx
    addq    $1, %rdx
    movq    %rdx, __gcov0._ZN7MyClass19non_throwing_methodEv+40(%rip)
    .loc 2 13 0
    movq    %rax, %rdi
    call    __cxa_call_unexpected
.LEHE2:
.L3:
    .loc 2 22 0
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1253:
    .globl  __gxx_personality_v0
    .section    .gcc_except_table._ZN7MyClass19non_throwing_methodEv,"aG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
    .align 4
.LLSDA1253:
    .byte   0xff
    .byte   0x3
    .uleb128 .LLSDATT1253-.LLSDATTD1253
.LLSDATTD1253:
    .byte   0x1
    .uleb128 .LLSDACSE1253-.LLSDACSB1253
.LLSDACSB1253:
    .uleb128 .LEHB0-.LFB1253
    .uleb128 .LEHE0-.LEHB0
    .uleb128 .L8-.LFB1253
    .uleb128 0x1
    .uleb128 .LEHB1-.LFB1253
    .uleb128 .LEHE1-.LEHB1
    .uleb128 .L9-.LFB1253
    .uleb128 0x3
    .uleb128 .LEHB2-.LFB1253
    .uleb128 .LEHE2-.LEHB2
    .uleb128 0
    .uleb128 0
.LLSDACSE1253:
    .byte   0x1
    .byte   0
    .byte   0x7f
    .byte   0
    .align 4
    .long   0

.LLSDATT1253:
    .byte   0
    .section    .text._ZN7MyClass19non_throwing_methodEv,"axG",@progbits,_ZN7MyClass19non_throwing_methodEv,comdat
    .size   _ZN7MyClass19non_throwing_methodEv, .-_ZN7MyClass19non_throwing_methodEv
    .section    .text._ZN7MyClass15throwing_methodEv,"axG",@progbits,_ZN7MyClass15throwing_methodEv,comdat
    .align 2
    .weak   _ZN7MyClass15throwing_methodEv
    .type   _ZN7MyClass15throwing_methodEv, @function

最佳答案

看来我要过一会儿再回答我自己的问题。

throw()noexcept 之间的区别(从 C++11 到 C++17)非常大。

在运行时,如果异常将函数标记为 throw() 调用堆栈展开到函数的调用者,然后调用 std::unexpected 来执行意外处理程序,最后(默认行为)程序由 std::terminate.

对于 C++11,noexcept 运行时行为略有不同:调用堆栈只是可能在程序执行终止之前展开。没有调用 std::unexpected。通常编译器只调用 std::terminate 而不会展开。

就是这样,这就是为什么 throw()noexcept 函数生成不同的汇编代码以及代码覆盖工具测量不同结果的原因。

幸运的是,从 C++17 开始,就不会再有惊喜了,因为动态异常规范已被弃用并从标准中删除,而 throw() 规范仍然有效,但它与 noexcept(true).

关于c++ - GCC/GCOV 为使用 throw()/noexcept 的函数生成的不同分支覆盖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43278010/

相关文章:

c++ - 有关 "cannot instantiate abstract class"的更多信息

c++ - 我不明白 DoNotOptimizeAway 的定义

c++11 - 如何使用带有-std = c++ 11标志的g++进行编译?

c++ - 跨动态库的静态*模板*类成员

c++ - ‘<temporary>’ 的修饰不是常量表达式

c++ - 在 C++ 中使用无符号变量的真正优势是什么?

c++ - 为什么 remove_if( ..., lambda ) 表达式需要赋值运算符?

apache - 在 RHEL5 中配置 gcc - 在 $PATH 中找不到可接受的 C 编译器

c++ - 在 try-catch block 中包装循环会导致性能问题吗?

c++ - 为什么 boost::optional 对于继承虚函数的类会失败