2010-05-11 8 views
12

Je lance gcov sur du code C avec une instruction switch. J'ai écrit des cas de test pour couvrir tous les chemins possibles à travers cette instruction switch, mais il signale toujours une branche dans l'instruction switch comme non prise et moins de 100% sur la statistique «Pris au moins une fois».Instructions gcov et switch

Voici quelques exemples de code pour démontrer:

#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; 
} 

J'ai construit avec "gcc temp.c -fprofile-arcs -ftest-coverage", a couru "a", puis a fait "gcov -b -c temp.c". La sortie indique huit branches sur le commutateur et une (branche 6) non prise.

Quelles sont toutes ces branches et comment puis-je obtenir une couverture de 100%?

+0

Le contenu du fichier .gcda est-il utile? – Cascabel

Répondre

0

J'utilise mingw sur windows (ce qui n'est pas le dernier gcc) et on dirait que ça peut être trié dans les nouvelles versions de gcc.

1

Êtes-vous sûr d'utiliser a.out? Voici mes résultats (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' 
+0

il a dit 'couru 'un' ', ce qui me suggère qu'il a couru' a.exe' et qu'il utilise Windows. – nategoose

+0

Je suppose - par défaut j'associe gcc avec unix. Les résultats semblaient consistants avec l'exécution d'une version différente de l'exécutable par accident. – ergosys

+0

Oui, j'utilise MinGW sous Windows, qui est gcc 3.4.5. Serait-ce quelque chose qui est corrigé dans les versions plus récentes de gcc? – Matt

2

j'obtenir le même résultat en utilisant gcc/gcov 3.4.6.

Pour une instruction switch, elle devrait normalement générer deux branches pour chaque instruction case. L'un est si le cas est vrai et devrait être exécuté, et l'autre est une branche "fallthrough" qui passe à l'affaire suivante.

Dans votre situation, il semble que gcc fasse une branche "fallthrough" pour le dernier cas, ce qui n'a aucun sens car il n'y a rien à craindre.

Voici un extrait du code assembleur généré par gcc (j'ai changé quelques-unes des étiquettes pour une meilleure lisibilité):

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 

Je reconnais que je ne connais pas grand-chose sur le montage x86, et je ne comprendre l'utilisation de l'étiquette L7, mais cela pourrait avoir quelque chose à voir avec la branche supplémentaire. Peut-être que quelqu'un avec plus de connaissances sur gcc peut expliquer ce qui se passe ici.

Il semble que ce soit un problème avec l'ancienne version de gcc/gcov, la mise à niveau vers un gcc/gcov plus récent pourrait résoudre le problème, particulièrement étant donné l'autre post où les résultats semblent corrects.

+0

Je doute que gcov fasse une branche de rattrapage; il semble plus probable que gcc le fasse. Que se passe-t-il si vous activez l'optimisation? –

+0

Il n'y a rien de mal à avoir un fallthrough pour 'default': après tout, il pourrait y avoir un' case' ci-dessous pour éviter d'exécuter le code spécifique 'default'. Ce qui ne va pas, c'est d'avoir une longueur d'avance pour le dernier énoncé du changement, puisqu'il n'y a rien à craindre. –

+0

@Brooks L'activation de l'optimisation ne change pas le problème, et il semble que si j'utilise -O3 ou plus, cela ajoute plus de branches. – WildCrustacean

4

Oh, oh! Le vidage d'assemblage de bde montre que cette version de GCC compile cette instruction switch comme une approximation d'une arborescence binaire, en commençant au milieu de l'ensemble. Donc, il vérifie si i est égal à 2, puis vérifie s'il est supérieur ou inférieur à 2, et ensuite pour chaque côté il vérifie s'il est égal à 1 ou 3 respectivement, et sinon, il passe à la valeur par défaut.

Cela signifie qu'il ya deux chemins de code différents pour arriver à ce résultat par défaut - un pour un plus grand nombre de 2 qui ne sont pas 3, et un pour les nombres inférieurs à 2 qui ne sont pas 1.

On dirait que vous obtiendrez une couverture de 100% si vous modifiez ce i<4 dans votre boucle à i<=4, afin de tester le chemin de chaque côté.

(Et, oui, c'est quelque chose qui est très susceptible d'avoir changé de GCC 3.x à GCC 4.x. Je ne dirais pas qu'il est "fixe", car ce n'est pas "faux" exactement en dehors de faire le gcov C'est juste que sur un processeur moderne avec prédiction de branchement, c'est probablement lent aussi bien que trop compliqué.)