OVH Cloud OVH Cloud

STL et fuite mémoire

12 réponses
Avatar
Bonjour à tous.

Je travaille sous linux, mon compilo est g++ en version 3.3.2
et quand je trace le code suivant, code on ne peut plus simple, valgrind
m'indique une fuite mémoire.

Voici le code :

#include <list>

using namespace std;

int main () {
list<int> l;
l.push_front(1);
l.push_front(2);
return 0;
}

et voici ce que me dit valgrind lancé avec les options --leak-check=yes
et --show-reachable=yes

==4698== Memcheck, a memory error detector for x86-linux.
==4698== Copyright (C) 2002-2003, and GNU GPL'd, by Julian Seward.
==4698== Using valgrind-2.1.0, a program supervision framework for
x86-linux.
==4698== Copyright (C) 2000-2003, and GNU GPL'd, by Julian Seward.
==4698== Estimated CPU clock rate is 2093 MHz
==4698== For more details, rerun with: -v
==4698==
==4698==
==4698== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==4698== malloc/free: in use at exit: 640 bytes in 1 blocks.
==4698== malloc/free: 1 allocs, 0 frees, 640 bytes allocated.
==4698== For counts of detected errors, rerun with: -v
==4698== searching for pointers to 1 not-freed blocks.
==4698== checked 4569216 bytes.
==4698==
==4698== 640 bytes in 1 blocks are still reachable in loss record 1 of 1
==4698== at 0x4002AF9B: operator new(unsigned) (vg_replace_malloc.c:162)
==4698== by 0x402BAB78: std::__default_alloc_template<true,
0>::_S_chunk_alloc(unsigned, int&) (in /usr/lib/libstdc++.so.5.0.5)
==4698== by 0x402BAA6C: std::__default_alloc_template<true,
0>::_S_refill(unsigned) (in /usr/lib/libstdc++.so.5.0.5)
==4698== by 0x402BA661: std::__default_alloc_template<true,
0>::allocate(unsigned) (in /usr/lib/libstdc++.so.5.0.5)
==4698==
==4698== LEAK SUMMARY:
==4698== definitely lost: 0 bytes in 0 blocks.
==4698== possibly lost: 0 bytes in 0 blocks.
==4698== still reachable: 640 bytes in 1 blocks.
==4698== suppressed: 0 bytes in 0 blocks.


Voilà là je comprends vraiment pas, je suis débutant en C++ d'accord
mais je maîtrise bien le C et là j'avoue que je mettrai ma tête
à couper que l'erreur ne vient pas de moi :-)

Ma question est donc : est-ce que la STL "fuit" ?
Ca me paraît fou pour une bibliothèque standard mais j'avoue
qu'autrement je ne vois pas d'autre explication possible.

Merci d'éclairer ma lanterne.

François-Gérard Radet

2 réponses

1 2
Avatar
kanze
Fabien LE LEZ wrote:
On Wed, 22 Dec 2004 13:40:30 +0100, "@(none)" <""fg"@(none)">:

Au fait, qu'entendez vous par "fuite à proprement parler" ?
Ce code fuit bien, non ?


Je vais peut-être faire hurler, mais libérer la mémoire à la fin
de

l'exécution d'un programme n'est pas forcément une bonne idée :
ça

prend du temps, et l'OS le ferait sans doute tout seul plus
efficacement.


Ça dépend de l'OS. Ce n'était pas le cas sous MS-DOS, par exemple,
et je
ne sais pas si c'était le cas sous Windows 95 et ses successeurs.
C'est
le cas de Unix, et je crois aussi de Windows NT et ses successeurs. (Au
fond, je m'attendrais à ce que ce soit le cas sur tout système
multi-utilisateur.)

J'ai bien des cas où je ne libère pas la mémoire exprès, surtout
des
Singleton alloués dynamiquement. Mais dans ces cas, je sais que le
code
n'est pas portable, et en fait ne tournerait que sur des systèmes
Unix. (Il y a un pthread_mutex_t juste à côté, par exemple.)

Fais tourner ton programme de façon intensive pendant 24 heures. Si
la

consommation mémoire reste constante (ou, du moins, en rapport avec
l'utilisation réelle), il n'y a pas de fuite mémoire.


Ça dépend. Supposons que la fuite, ce n'est que dans le cas d'un
certain
type d'erreur. Qui n'apparaît pas assez souvent pour que la fuite soit
remarquée dans 24 heures. Mais sur plusieurs années...

Il n'y a pas de solution parfaite : on vérifie lors des révues de
code,
on vérifie dans les tests unitaires (y compris en cas d'erreur) avec
des
outils qui convient, ET on vérifie en grandeur réele sur une semaine
au
moins (toujours avec des outils du genre Purify).

Un outil de vérification digne de ce nom aurait prévu le cas des
«@pseudo-statiques ». Avec Purify, par exemple, on peut mettre des
filtres, pour supprimer le message d'erreur dans les cas comme ça. Du
coup, il faut bien analyser l'erreur la première fois, mais une fois
qu'on a déterminé que c'est anodin.

Si au contraire la consommation mémoire augmente régulièrement, tu
as

un problème sur les bras.


Ça, de toute façon.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Loïc Joly wrote:
none wrote:
Avant de parler de fuite à proprement parler, il faut bouger ce
code dans une fonction, et appeler cette fonction dans un boucle
et



voir si la quantité de mémoire "still reachable" augmente.



[...]

Au fait, qu'entendez vous par "fuite à proprement parler" ? Ce
code


fuit bien, non ? En effet, la fuite ne m'est pas imputable mais au
bout du compte il y a bien fuite.


Je n'appelle pas ça une fuite. Prenons par exemple un sujet
différent

(car je ne sais pas spécifiquement ce que fait l'implémentation de
list en question) :


Ce n'est pas un nom standard, mais j'appelle ça personnellement les
pseud-statiques. C'est la mémoire qui est allouée une seule fois,
quelque soit la fréquence d'utilisation de l'objet ou de la fonction.
Au
fond, il aurait pû être statique, sauf que typiquement, on ne
connaît
pas sa taille réele avant le lancement du programme.

Une fonction sinus, lors de son premier appel, crée une table de
valeurs pour aller plus vite à calculer des sinus lors des appels
successifs. Et bien entendu, elle ne va pas libérer cette table
après

son exécution, ça ne servirait à rien.


Et bien sûr, dans la réalité, tu déclareras ce tableau statique ;
tu
n'alloueras pas.

Le vrai problème, c'est précisement avec des allocateurs. Donc, par
exemple, dans mon implémentation pré-standard d'une liste, je me
servais
d'un pool d'éléments, pour que l'allocation aille plus vite. Mais
évidemment, je ne savais pas le nombre d'éléments maximum qu'il y
aurait
dans la liste. J'allouais donc des gros blocs de mémoire d'un coup,
qui
servait ensuite aux éléments. C'est une solution plus ou moins
standard,
mais non sans problèmes :

-- Une foie allouée pour un élément, la mémoire peut resservir
pour
d'autres éléments, mais pas pour d'autre chose. (Dans mon cas,
j'avais une option pour qu'il y a un pool par liste, qui était
libéré dans le destructeur de liste, plutôt qu'un pool global.
Voir
par exemple mon implémentation de FixedLengthAllocator, à ma
site.)

-- Si j'oublie dans certains cas particuliers à libérer un
élément,
j'ai aussi une fuite de mémoire. Mais puisque j'alloue des blocs
que
je ne libère jamais, j'ai configuré mon outil de vérification
des
fuites à ignorer ces fuites. C'est donc beaucoup plus difficile à
detecter une telle fuite.

Note finalement qu'une fuite n'est pas forcement un problème. Si ton
programme traite un ensemble de données (un compilateur, par exemple),
puis s'en va, et qu'il tourne sur un système d'exploitation qui
libère
en fait toutes les ressources du programme quand il termine, une fuite
est en général anodine. S'il s'agit d'un logiciel avec un GUI, que
l'utilisateur laisse actif toute la journée, mais qui rédémarre
quand
même tous les jours, il n'y a que les grosses fuites qui risquent de
poser un problème. En revanche, si c'est un serveur, qui tourne 24
heures sur 24, 7 jours par semaine, pendant des années, la plupart des
fuites sont fatales. (Je dis la plupart, parce que si tu fuis 16 octets
chaque fois que tu ajoutes le 29 février dans une année bisextile,
sur
une machine moderne, cette fuite ne va pas te tuer non plus.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



1 2