C++ et destructeur non virtual

A un moment j'ai eu un patron qui demandait tout le temps à tout le monde s'il faisait de l'algo... Je trouvais ça bizarre comme critère principal de recrutement, et puis un jour en discutant avec lui j'ai compris : "ce qu'il y a de bien avec l'algo, c'est que tu peux prouver que ton code est juste, sans même avoir besoin de l'exécuter".
Ce qui est dommage avec lui, c'est qu'on a perdu énormément de temps à débugger son code ^^ (il ne semblait pas capable de le faire lui-même). Pour un jeu DS qu'on a eu à faire (et dont je tairais le nom afin de préserver le peu de crédibilité dont je pourrais disposer), il avait mis en place tout un système de widget, façon Java Swing, pour gérer les menus du jeu. Bon, c'est super, je ne suis pas forcément contre, mais le menu en question c'était un écran avec 2 boutons (PLAY et OPTIONS), l'écran des options c'était 2 boutons (SOUND ON/OFF et CREDITS), et l'écran de crédits avec les noms qui défilent. 2-3 mois pour faire ça, je trouve ça long ^^ Surtout qu'au final, je me suis rendu compte que si on faisait la séquence suivante : menu -> jeu -> menu -> jeu, ça plantait par manque de mémoire.
Là, on est sur DS... Donc pas de Valgrind pour analyser la situation. En regardant le code, je ne voyais pas trop d'où venait le souci, tous les objets alloués avaient l'air d'être détruits... Du coup je me suis dit que j'allais tracer toutes les allocations/libérations afin de trouver l'origine du problème. Heureusement pour nous, on n'utilisait pas malloc et free, mais des fonctions Mem_Alloc et Mem_Free persos, qui appelaient des routines d'allocation/libération de la DS, et qui étaient aussi utilisés en C++ par 'new' et compagnie... Je rajoute donc des affichages de debug à en spammer la console :
A 0xXXXXXXXX (NN bytes) pour les allocations F 0xXXXXXXXX pour les libérations
Je me retrouve donc avec une console remplie de milliers d'allocations/libérations. Juste ingérable ^^ Je rajoute donc un premier filtre qui permet de n'afficher que les allocations supérieurs à une certaines taille (genre 1ko je crois), car de toute façon on avait des fuites de l'ordre de 500ko... Ca faisait encore pas mal de données à traiter, du coup j'ai fait une petite appli vite fait qui prend en ligne de commande un fichier, qui le dump ligne par ligne, enregistre les alloc/free, et recrache tout ce qui fuit (en gros le boulot de Valgrind...). Bien pratique tout ça :p Car avec ça j'avais la liste de toutes les allocations qui fuyaient, et comme sur DS on pouvait mettre des breakpoints, j'ai pu mettre des
if (adr == 0xXXXXXXXX){
printf("STOP HERE\n");
}
avec un breakpoint sur le printf...
Et là, je me rends compte que tous les trucs qui fuient sont en fait les widgets... La question est donc : pourquoi ? Parce que dans le code, on fait bien des "delete" sur ces objets...
La réponse a été trouvée sur le net : si un destructeur n'est pas déclaré virtual, on ne passe pas par le destructeur de la classe fille ! C'est tout con, mais il faut le savoir, sinon niveau fuite ça peut aller très vite (comme là).
Problème réglé !! (modulo quelques petites fuites ailleurs mais plus classiques, oublis de libération de la mémoire...)
Moralité : d'une part on peut prouver qu'un algo est juste et avoir une implémentation fausse (car se sont 2 choses différentes), d'autre part, on peut prouver qu'un algo est juste tout en ayant une faille dans le raisonnement, et donc la preuve est en fait fausse... Je me dis que ce n'est pas pour rien que JUnit est utilisé par pas mal de monde...
Et aussi : utiliser les bons outils (Valgrind ou autre) pour résoudre les bons problèmes, c'est top ! Ici, en l'occurence, il aura été plus rapide de coder un petit tool sur mesure plutôt que de chercher vainement partout dans le code...
Aucun trackbacks pour l'instant