tags:

views:

214

answers:

4

I'm running gcov over some C code with a switch statement. I've written test cases to cover every possible path through that switch statement, but it still reports a branch in the switch statement as not taken and less than 100% on the "Taken at least once" stat.

Here's some sample code to demonstrate:

#include "stdio.h"

void foo(int i)
{
    switch(i)
    {
        case 1:printf("a\n");break;
        case 2:printf("b\n");break;
        case 3:printf("c\n");break;
        default: printf("other\n");
    }
}

int main()
{
    int i;
    for(i=0;i<4;++i)
        foo(i);
    return 0;
}

I built with "gcc temp.c -fprofile-arcs -ftest-coverage", ran "a", then did "gcov -b -c temp.c". The output indicates eight branches on the switch and one (branch 6) not taken.

What are all those branches and how do I get 100% coverage?

+1  A: 

Are you sure you are running a.out? Here is my results (gcc 4.4.1):

File 't.c'
Lines executed:100.00% of 11
Branches executed:100.00% of 6
Taken at least once:100.00% of 6
Calls executed:100.00% of 5
t.c:creating 't.c.gcov'
ergosys
@ergosys: he said `ran "a"`, which to me suggests that he ran `a.exe` and is using windows.
nategoose
I suppose--by default I associate gcc with unix. The results looked consistant with running a different version of the executable by accident.
ergosys
Yes, I'm using MinGW on Windows, which is gcc 3.4.5. Could this be something that's fixed in more recent versions of gcc?
Matt
+1  A: 

I get the same result using gcc/gcov 3.4.6.

For a switch statement, it should normally generate two branches for each case statement. One is if the case is true and should be executed, and the other is a "fallthrough" branch that goes on to the next case.

In your situation, it looks like gcc is making a "fallthrough" branch for the last case, which doesn't make sense since there is nothing to fall into.

Here's an excerpt from the assembly code generated by gcc (I changed some of the labels for readability):

    cmpl    $2, -4(%ebp)
    je  CASE2
    cmpl    $2, -4(%ebp)
    jg  L7
    cmpl    $1, -4(%ebp)
    je  CASE1
    addl    $1, LPBX1+16
    adcl    $0, LPBX1+20
    jmp DEFAULT
L7:
    cmpl    $3, -4(%ebp)
    je  CASE3
    addl    $1, LPBX1+32
    adcl    $0, LPBX1+36
    jmp DEFAULT

I admit that I don't know much about x86 assembly, and I don't understand the use of the L7 label but it might have something to do with the extra branch. Maybe someone with more knowledge about gcc can explain what is going on here.

It sounds like it might be an issue with the older version of gcc/gcov, upgrading to a newer gcc/gcov might fix the problem, especially given the other post where the results look correct.

bde
I doubt that's gcov making a fallthrough branch; it looks more likely to be gcc doing it. What happens if you turn on optimization?
Brooks Moses
There is nothing wrong with having a fallthrough for `default`: after all there could be a `case` below to avoid executing the code specific to `default`. What's wrong is having a fallthrough for the last statement of the switch, since there isn't anything to fall into.
Matthieu M.
@Brooks Turning on optimization doesn't change the problem, and it looks like if I use -O3 or above it adds more branches.
bde
@Matthieu I agree, that makes more sense. I've edited my response to reflect that.
bde
@bde: Ah, well. So much for that idea!
Brooks Moses
@Brooks: It was a good idea. I posted an excerpt from the assembly code in my answer, maybe somebody that knows more about gcc can explain what is going on.
bde
@bde: Thanks! That assembly dump does indeed appear to explain things; see the answer I posted.
Brooks Moses
+2  A: 

Oho! bde's assembly dump shows that that version of GCC is compiling this switch statement as some approximation of a binary tree, starting at the middle of the set. So it checks if i is equal to 2, then checks if it's greater or less than 2, and then for each side it checks if it's equal to 1 or 3 respectively, and if not, then it goes to default.

That means there are two different code paths for it to get to the default result -- one for numbers higher than 2 that aren't 3, and one for numbers lower than 2 that aren't 1.

Looks like you'll get to 100% coverage if you change that i<4 in your loop to i<=4, so as to test the path on each side.

(And, yes, that's something that's very likely to have changed from GCC 3.x to GCC 4.x. I wouldn't say it's "fixed", as it's not "wrong" exactly aside from making the gcov results confusing. It's just that on a modern processor with branch prediction, it's probably slow as well as overly complicated.)

Brooks Moses
A: 

I'm using mingw on windows (which is not the latest gcc) and it looks like this may be sorted out in newer versions of gcc.

Matt