GCC/GCOV 为 使用 throw() / noexexcept 函数生成的不同分支覆盖
Different branch coverage generated by GCC/GCOV for function using throw() / noexcept
我想了解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 个测试用例,均通过:
- 验证
non_throwing_method
是否在底层throwing_method
未引发时完成, - 验证
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进行比较)。
除了:
_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)
相同。
- 专门化模板覆盖函数/避免对象切片
- 为什么编译器不检查被覆盖函数的存储类?
- 如何使基类从子类调用覆盖函数
- 为什么 MSVC 在使用正确的签名覆盖函数时会产生 C3668 错误?
- Clang++ 6.0 内存清理器未报告返回值指示条件分支的函数中的未初始化局部变量
- 为什么调用具有通用或 r 值引用的重载覆盖函数是不明确的?
- 如何在C++中覆盖函数
- Arduino 可覆盖函数
- GCC/GCOV 为 使用 throw() / noexexcept 函数生成的不同分支覆盖
- 为什么我们不能在C++中覆盖函数指针?
- 在多级继承中重写,中间派生类未覆盖函数
- C++获取指向受保护覆盖函数的指针
- 内联失败:可以在链接时覆盖函数体
- 生成lcov的分支覆盖数据
- C++ 调用覆盖函数会导致调用基函数
- C++虚拟覆盖函数链接器错误
- 如何使用函数 b(C & winapi)覆盖函数 a?
- 用于在多重继承中覆盖函数的语法
- Cocos2DX 中的覆盖函数
- 使用覆盖函数的继承