neverlord neverlord - 2 months ago 11
C++ Question

How to suppress inlining of templates with gcov

I'm using GCC 4.9 with GCOV to get code and branch coverage. However, the results for branch coverage are utterly useless for my C++ code. It seems GCC inlines templates despite using all

-fno-*-inline
flags I know of.

Here is a small example application that illustrates the problem:

#include <string>
#include <iostream>

using namespace std;

int main() {
string foo;
foo = "abc";
cout << foo << endl;
}


I compile the program with
g++ -O0 -fno-inline -fno-inline-small-functions -fno-default-inline --coverage -fprofile-arcs test.cpp -o test


After running
test
,
gcovr -r . -b
prints:

------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Branches Taken Cover Missing
------------------------------------------------------------------------------
test.cpp 14 7 50% 7,8,9,10
------------------------------------------------------------------------------
TOTAL 14 7 50%
------------------------------------------------------------------------------


There is not a single branch in our
main
function. For example, line 7 contains
string foo;
. It seems the constructor of
std::basic_string<...>
has some if statement in it, but that's not useful information when looking at the coverage of
main
.

The problem is that all these inlined branches sum up and the branch coverage calculated for my actual unit tests are about 40% as a result. I'm interested in the branch coverage of my code, as opposed to how much branches I hit in the C++ standard library.

Is there any way to completely shut down inlining in the compiler or to tell GCOV to not consider inlined branches? I couldn't find any guide on the GCOV homepage or someplace else regarding that topic.

Any help is much appreciated.

Answer

Well, you should always double-check your expectations. Thanks a lot @Useless for pointing me to the gcov output itself. You weren't quite right, though: the branches are not attributed to the test.cpp file. Running gcovr with -k and looking at all the intermediate files shows that gcov correctly produces files such as #usr#include#c++#4.9#bits#basic_string.h.gcov that show coverage for the C++ standard library side of things.

However, the reason for all the branches in test.cpp is not inlining. It's exceptions. Each call into the standard library is a branch because of potential exceptions (e.g. std::bad_alloc). Adding -fno-exceptions to the compiler flags gives the following output:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                    Branches   Taken  Cover   Missing
------------------------------------------------------------------------------
test.cpp                                       4       2    50%   10
------------------------------------------------------------------------------
TOTAL                                          4       2    50%
------------------------------------------------------------------------------

Digging deeper into the gcov output via cat foo.cpp.gcov prints:

        -:    0:Source:test.cpp
        -:    0:Graph:/home/neverlord/gcov/test.gcno
        -:    0:Data:/home/neverlord/gcov/test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <string>
        -:    2:#include <iostream>
        -:    3:
        -:    4:using namespace std;
        -:    5:
function main called 1 returned 100% blocks executed 100%
        1:    6:int main() {
        1:    7:  string foo;
call    0 returned 1
        1:    8:  foo = "abc";
call    0 returned 1
        1:    9:  cout << foo << endl;
call    0 returned 1
call    1 returned 1
call    2 returned 1
function _GLOBAL__sub_I_main called 1 returned 100% blocks executed 100%
function _Z41__static_initialization_and_destruction_0ii called 1 returned 100% blocks executed 100%
        4:   10:}
call    0 returned 1
branch  1 taken 1 (fallthrough)
branch  2 taken 0
branch  3 taken 1 (fallthrough)
branch  4 taken 0

Sorry for the noise.

Comments