GCC-gcov code coverage does not consider the branches in your source code, it considers the branches emitted by the compiler. Approximately, it works on an assembly level. If I look at the assembly for your initial func()
, I see three branching points associated with that line:
func():
push rbp
mov rbp, rsp
call a()
test eax, eax
jne .L6
call b()
test eax, eax
je .L7
.L6:
mov eax, 1
jmp .L8
.L7:
mov eax, 0
.L8:
test al, al
je .L9
mov eax, 1
jmp .L10
.L9:
mov eax, 0
.L10:
pop rbp
ret
– https://godbolt.org/z/6q691r3fj gcc 12.2 with -O0
The entire .L8
and .L9
block look pretty pointless to me, but it seems to me that the compiler is actually translating the conditional via an intermediate variable. Roughly, your code
if (a() || b()) {
return 1;
} else {
return 0;
}
seems to be translated as
int cond;
if (a() || b()) {
cond = 1;
} else {
cond = 0;
}
int retval;
if (cond) {
retval = 1;
} else {
retval = 0;
}
return retval;
This is just the kind of code that gets emitted when disabling all optimizations. It is not always a literal translation of the source code, sometimes compilers do stuff that appears really dumb. As discussed in the gcovr FAQ entry “Why does C++ have so many uncovered branches?”, it is not generally possible to get rid of all the compiler-generated branches. As a consequence, chasing a concrete branch coverage metric is not overly useful for GCC-gcov based coverage data.