Dans un cadre typique de la mise en œuvre, le destructeur est habituellement de deux branches: l'une pour les non-dynamique de la destruction des objets, un autre pour la dynamique de la destruction des objets. La sélection d'une branche spécifique est effectuée par un caché paramètre booléen passé pour le destructeur par l'appelant. Elle est habituellement transmise par l'intermédiaire d'un registre, soit 0 ou 1.
Je suppose que, puisque dans votre cas, la destruction est un non-objet dynamique, la dynamique de la branche n'est pas pris. Essayez d'ajouter un new
-ed et puis, delete
-ed objet de la classe Foo
et la seconde branche devrait devenir ainsi.
La raison de cette ramification est nécessaire est enracinée dans la spécification du langage C++. Lorsqu'une classe définit ses propres operator delete
, le choix du type operator delete
d'appel est fait comme si c'était de l'intérieur de la classe destructeur. Le résultat final est que pour les classes avec un destructeur virtuel operator delete
se comporte comme si elle était un virtuel de la fonction (malgré officiellement un statique membre de la classe).
De nombreux compilateurs implémenter ce comportement littéralement: le bon operator delete
est appelée directement à partir de l'intérieur du destructeur de la mise en œuvre. Bien sûr, operator delete
ne doit être appelée lors de la destruction d' dynamiquement les objets alloués (pas pour les locaux ou les objets statiques). Pour atteindre cet objectif, l'appel à l' operator delete
est placé dans une branche contrôlée par le paramètre caché mentionnés ci-dessus.
Dans votre exemple, les choses semblent assez trivial. Je m'attends à de l'optimiseur de supprimer la ramification. Cependant, il semble qu'il en ait réussi à survivre à l'optimisation.
Voici un peu plus de recherche. Considérer ce code
#include <stdio.h>
struct A {
void operator delete(void *) { scanf("11"); }
virtual ~A() { printf("22"); }
};
struct B : A {
void operator delete(void *) { scanf("33"); }
virtual ~B() { printf("44"); }
};
int main() {
A *a = new B;
delete a;
}
C'est la façon dont le code pour le destructeur de l' A
va ressembler quand compilateur GCC 4.3.4 sous paramètres d'optimisation par défaut
__ZN1AD2Ev: ; destructor A::~A
LFB8:
pushl %ebp
LCFI8:
movl %esp, %ebp
LCFI9:
subl $8, %esp
LCFI10:
movl 8(%ebp), %eax
movl $__ZTV1A+8, (%eax)
movl $LC1, (%esp) ; LC1 is "22"
call _printf
movl $0, %eax ; <------ Note this
testb %al, %al ; <------
je L10 ; <------
movl 8(%ebp), %eax ; <------
movl %eax, (%esp) ; <------
call __ZN1AdlEPv ; <------ calling `A::operator delete`
L10:
leave
ret
(Le destructeur d' B
est un peu plus compliqué, c'est pourquoi j'utilise A
ici comme un exemple. Mais aussi loin que la ramification en question, destructeur d' B
t-il de la même manière).
Cependant, dès la fin de ce destructeur le code généré contient une autre version de l'destructeur pour la même classe d' A
, ce qui semble exactement le même, à l'exception de l' movl $0, %eax
instruction est remplacé par movl $1, %eax
enseignement.
__ZN1AD0Ev: ; another destructor A::~A
LFB10:
pushl %ebp
LCFI13:
movl %esp, %ebp
LCFI14:
subl $8, %esp
LCFI15:
movl 8(%ebp), %eax
movl $__ZTV1A+8, (%eax)
movl $LC1, (%esp) ; LC1 is "22"
call _printf
movl $1, %eax ; <------ See the difference?
testb %al, %al ; <------
je L14 ; <------
movl 8(%ebp), %eax ; <------
movl %eax, (%esp) ; <------
call __ZN1AdlEPv ; <------ calling `A::operator delete`
L14:
leave
ret
Remarque les blocs de code je étiquetés avec des flèches. C'est exactement ce dont je parlais. Inscrivez al
sert en tant que paramètre caché. Cette "pseudo-branche" est censé soit appeler ou d'ignorer l'appel d' operator delete
conformément à la valeur de al
. Cependant, dans la première version du destructeur de ce paramètre est codé en dur dans le corps, comme toujours, 0
, tandis que dans le second il est codé en dur, comme toujours, 1
.
Classe B
a également deux versions de l'destructeur généré pour lui. On se retrouve donc avec 4 distinctif destructeurs dans le programme compilé: deux destructeurs pour chaque classe.
Je peux deviner que, au début du compilateur interne de la pensée dans les termes d'un seul "paramétrée" destructeur (qui fonctionne exactement comme je l'ai décrit au-dessus de la pause). Et puis il a décidé de scinder le paramétrée destructeur en deux indépendants non paramétrée versions: l'une pour la codé en dur la valeur de paramètre 0
(non-dynamique destructeur) et l'autre pour la codé en dur la valeur de paramètre 1
(dynamique destructeur). En non-mode optimisé, il n'a, littéralement, par l'affectation de la valeur effective du paramètre à l'intérieur du corps de la fonction et en laissant toutes les branches totalement intact. Cela est acceptable dans la non optimisation de code, je suppose. Et c'est exactement ce que vous avez à faire.
En d'autres termes, la réponse à votre question est la suivante: Il est impossible de faire le compilateur de prendre toutes les branches dans ce cas. Il n'y a pas moyen d'atteindre une couverture de 100%. Certaines de ces branches sont "morts". C'est juste que l'approche de la génération de non-code optimisé est plutôt "paresseux" et "lâche" dans cette version de GCC.
Il y a peut être un moyen de prévenir la répartition des non-mode optimisé, je pense. Je n'en ai pas encore trouvé. Ou, très probablement, il ne peut pas être fait. Les anciennes versions de GCC utilisée vrai paramétrée destructeurs. Peut-être que dans cette version de GCC ils ont décidé de passer à deux destructeur approche et tout en le faisant, ils "réutilisés" le générateur de code dans de tels rapide et sale, attend l'optimiseur pour nettoyer les branches inutiles.
Lorsque vous avez activé l'optimisation de la compilation avec GCC ne pourra pas se permettre un tel luxe inutile de ramification dans le code final. Vous devriez probablement essayer d'analyser un code optimisé. Non optimisé GCC-code généré a beaucoup de sens inaccessible branches comme celui-ci.