Erreur vicelarde en C++

En voici une bien méchante qui n'est pas forcément simple à comprendre. Elle n'est donc forcément pas simple à corriger ;)

Codons ensemble mes frères

Prenons une hiérarchie de classes assez simple. Disons un CGobelin et un CDragon qui dérivent de CMonster. Chacun implémente la méthode virtuelle pure void Attack() qui est déclarée dans CMonster. En plus de cette méthode, chaque classe se définit des données supplémentaires en plus de celles de CMonster.

Ensuite, quelque part dans votre code, vous ajoutez une méthode vous permettant de récupérer tous les monstres. Comme vous êtes dans un langage assez bas niveau, vous vous dites qu'utiliser l'arithmétique des pointeurs vous ferait gagner un peu de performance. Allez hop, une jolie CMonster* GetMonsters(). Et vous utilisez alors le pointeur retourné comme un tableau.

Et là, c'est le drame. Tout explose, ou presque.

Jouons ensemble mes frères

Hop, le personnage se balade dans un forêt hantée (CHauntedForest) et rencontre son premier monstre. Le combat, malgré sa difficulté se passe sans trop de mal, même si vous devrez par la suite allez voir le prêtre du prochain village pour qu'il vous fasse repousser ce membre que vous venez de perdre et dont vous vous servez tant.

Bon le programme fonctionne bien, test terminé... Heu non. Attention. La fameuse méthode devait renvoyer un tableau de monstres, alors vous prenez votre courage à 2 mains (ou votre épée à 2 mains, au choix) et vous y retournez !

Et là, c'est vraiment le drame, votre magnifique quète s'achève ici dans un torrent de coredumps et d'erreurs de pagination. Votre personnage est mort, et votre programme aussi.

Débuggons ensemble mes... Hey ! Mais revenez !

Mais que s'est-il bien passé, vous avez pourtant bien utilisé votre superbe tableau de monstres. Et bien oui, justement, et là est le vrai drame.

L'arithmétique des pointeurs en C et en C++ se base tout simplement sur le type déclaré et est un simple calcul d'octets effectué par le compilateur. Quand vous utilisez votre pointeur sur un CMonster comme un tableau, le compilateur croit que vous allez parcourir un tableau de CMonster, et va donc incrémenter votre pointeur en fonction de la taille de CMonster et pas des types réels des objets pointés.

Ce qui fait que votre 1er objet sera toujours correcte, puisque vous y accédez avec le bon pointeur. Mais pour le 2e, c'est autre chose. Votre pointeur subit un offset correspondant à la taille d'un CMonster avec que vos objets sont des CDragons. Résultat : votre 2e objet pointé est en fait un infâme mutant à cheval entre 2 objets réels de votre tableau, mutant dans le sens où ses données ne sont pas du tout correctes, infâme, dans le sens où sa table des méthodes virtuelles est complètement fausse, entrainant sans aucun doute une mort rapide et sans douleur de votre programme au prochain appel de l'une de ces méthodes.

Moralité, on ne rappellera jamais assez que la performance ne doit pas être votre priorité lorsque vous développez. Il aurait été ici plus sage d'utiliser des méthodes d'accès aux données qui soient plus sûres comme un CMonster* GetAt(int32 nIndex).

Même si cette approche est plus lente, il sera toujours temps de modifier ces quelques détails lorsqu'un éditeur vous aura remis un contrat de plusieurs millions d'euros parce que votre jeu est « marrant, très stable, mais un peu lent ».

Haut de page