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

10 réponses

1 2
Avatar
Loïc Joly
none wrote:

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


Je ne comprend pas vraiment les sorties de Valgrind, mais je sais qu'il
est possible que our des raisons d'optimiation, la bibliothèque alloue
de la mémoire "en plus" qu'elle laisse alouée quand l'objet est détruit,
ce qui peut tromper les outils de ce type.

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.

--
Loïc

Avatar


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.



Bonjour,

j'ai créé la fonction en question :

#include <list>

using namespace std;

void foo() {
list<int> l;
l.push_front(1);
l.push_front(2);
}

int main () {
for (int i = 0; i < 10; i++) {
foo();
}
return 0;
}

en effet le fait de mettre ça dans dans une boucle n'augmente pas le
nombre de blocs non désalloués signalés par valgrind (il ne m'en signale
toujours qu'un).
Le problème c'est que dans le projet que je développe j'ai beaucoup de
liste dans des objets différents et là le nombre de fuites 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 ne comprend pas vraiment les sorties de Valgrind, mais je sais
qu'il
est possible que our des raisons d'optimiation, la bibliothèque alloue
de la mémoire "en plus" qu'elle laisse alouée quand l'objet est
détruit,
ce qui peut tromper les outils de ce type.



Connaitriez vous une façon simple d'empêcher ce comportement ?
Genre option de compilation par exemple.

Merci d'avance.

François-Gérard Radet

Avatar
Loïc Joly
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) :

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. Est-ce que cette fonction fuit ? Pour moi non. Pour
rester dans l'analogie avec les liquides, cette fonction utilise un
réservoir de liquide en interne, qu'elle ne vide jamais, mais ne fuit pas.

Je ne comprend pas vraiment les sorties de Valgrind, mais je sais
qu'il
est possible que our des raisons d'optimiation, la bibliothèque alloue
de la mémoire "en plus" qu'elle laisse alouée quand l'objet est
détruit,
ce qui peut tromper les outils de ce type.



Connaitriez vous une façon simple d'empêcher ce comportement ?
Genre option de compilation par exemple.


Non. Ce problème est-il vraiment gênant ? Si la mémoire constitue une
resosurce rare, peut-être que d'autres conteneurs que les listes (qui
demandent en général la taille de 2 pointeurs en plus par élément)
seraient plus appropriés ?


A part ça, j'ai trouvé ça dans la faq de valgrind :

4.3. My program uses the C++ STL and string classes. Valgrind
reports 'still reachable' memory leaks involving these classes
at the exit of the program, but there should be none.

First of all: relax, it's probably not a bug, but a feature. Many
implementations of the C++ standard libraries use their own memory pool
allocators. Memory for quite a number of destructed objects is not
immediately freed and given back to the OS, but kept in the pool(s) for
later re-use. The fact that the pools are not freed at the exit() of
the program cause Valgrind to report this memory as still reachable.
The behaviour not to free pools at the exit() could be called a bug of
the library though.

Using gcc, you can force the STL to use malloc and to free memory as
soon as possible by globally disabling memory caching. Beware! Doing
so will probably slow down your program, sometimes drastically.

- With gcc 2.91, 2.95, 3.0 and 3.1, compile all source using the STL
with -D__USE_MALLOC. Beware! This is removed from gcc starting with
version 3.3.

- With 3.2.2 and later, you should export the environment variable
GLIBCPP_FORCE_NEW before running your program.

There are other ways to disable memory pooling: using the malloc_alloc
template with your objects (not portable, but should work for gcc) or
even writing your own memory allocators. But all this goes beyond the
scope of this FAQ. Start by reading
http://gcc.gnu.org/onlinedocs/libstdc++/ext/howto.html#3 if you
absolutely want to do that. But beware:

1) there are currently changes underway for gcc which are not totally
reflected in the docs right now ("now" == 26 Apr 03)

2) allocators belong to the more messy parts of the STL and people went
at great lengths to make it portable across platforms. Chances are
good that your solution will work on your platform, but not on
others.


--
Loïc


Avatar
Stanislas RENAN
Bonjour,

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.
je ne connais pas le produit mais quand je lis :


ù8== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


puis

ù8== LEAK SUMMARY:
ù8== definitely lost: 0 bytes in 0 blocks.
ù8== possibly lost: 0 bytes in 0 blocks.
ù8== still reachable: 640 bytes in 1 blocks.
ù8== suppressed: 0 bytes in 0 blocks.


je ne lis pas qu'il y a une fuite mémoire, mais qu'il y a toujours de la
mémoire allouée au moment au l'outil finit son analyse.

Pourquoi il ne peut pas poursuivre son analyse, je ne sais pas.

Ah, tiens, la réponse est dans la FAQ de l'outil :
<http://valgrind.kde.org/faq.html>

--
Stanislas RENAN

Avatar
Jean-Marc Bourguet
"@(none)" <""fg"@(none)"> writes:

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" ?


"Still reachable" veut dire que la memoire n'a pas ete liberee mais
qu'il existe au moment de la sortie du programme des pointeurs vers
elle. A priori ce n'est pas un probleme car normalement les OS
recupere toute la memoire a la sortie d'un process (ce n'est pas
toujours vrai, dans certains ca c'est un bug de l'OS, dans l'autre --
par exemple gestion de memoire partagee -- c'est une feature).

C'est quoi fuire de la memoire? La version la plus stricte, "de la
memoire qui ne sera plus utilisee mais qui n'est pas liberee". Mais
il y a different niveau de liberation.
- a l'OS
- a run-time qui gere la memoire
- a un pool, free list ou autre systeme de gestion de memoire plus
"performant".

Dans le cas qui t'occupe, valgrind reporte comme "still reachable" ce
qui n'est pas redonne au run-time (c'est assez rare que celui-ci rende
a l'OS avant la sortie du programme) et je parie que std::list<> (j'ai
pas ete voir l'implementation), ou plus exactement l'allocateur,
utilise un systeme de pool ou de free list. A priori celle-ci est
commune a tout std::list<> instancie avec les memes parametres. Si tu
as beaucoup de types de liste utilises mais pas en meme temps, et que
tu as un probleme de memoire, tu peux peut-etre essayer de definir ton
propre allocateur qui convient mieux a ton usage.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
OK OK je comprends mieux maintenant !

En fait la seule chose qui me chagrinait un peu c'était ça :

=P36== malloc/free: 1 allocs, 0 frees, 640 bytes allocated.

cad pas autant de free que de malloc ce qui était pour moi synonyme de
fuite mémoire.
Maintenant si c'est un comportement normal de la STL ya pas de pb :-)

Merci à vous trois et en particulier à Loïc Joly pour son explication
avec une implémentation de la fonction sinus et son analogie poussée
avec les liquides ;-)

François-Gérard Radet
Avatar
Fabien LE LEZ
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.

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.
Si au contraire la consommation mémoire augmente régulièrement, tu as
un problème sur les bras.


--
;-)

Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

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.


C'est pas moi que tu vas faire hurler avec ca. Une possibilite, c'est
de ne liberer que quand on tourne un outil de recherche de fuite.
Comme ca on peut detecter des problemes avec de la memoire dont on
garde un pointeur sans le savoir... c'est parfois des fuites quand
meme (en ce sens qu'elle aurait ete recuperable a l'interieur d'une
execution et qu'elle ne l'est pas).

Pour la gestion des pools, suivant l'outil utiliser on peut lui
indiquer qu'on a des pools et alors il detecte les mismatch de
free/delete..., ou alors on peut aussi les desactiver.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Stephane Wirtel
J'ai pû constater en utilisant des std::list et une classe propre à moi,
que les items de la liste, n'était pas désalloué. J'ai dû donc parcourir
la liste via un Iterator et faire appel au destructeur de la ma classe
via delete.

Ma construction était la suivante.

#include <iostream>

class Action {
public:
Action () {
std::cout << "Action::Action" << std::endl;
}
~Action () {
std::cout << "Action::~Action" << std::endl;
}
};

typedef std::list <Action *> ListAction;

int main () {
try {
ListAction::iterator p_lstActionIt;
ListAction lstAction;

lstAction.push_front (new Action);
lstAction.push_front (new Action);
lstAction.push_front (new Action);

for (p_lstActionIt = lstAction.begin (); p_lstActionIt = lstAction.end
(); ++p_lstActionIt) {
delete *p_lstActionIt;
*p_lstActionIt = 0;
}
} catch (const std::exception& except) {
std::cout << "Une erreur est survenue..." << std::endl;
}
return 0;
}

Il y a certainement plus simple en employant les algos fournis par la
STL, mais ceci n'est qu'un exemple.

Stéphane
Avatar
drkm
Stephane Wirtel writes:

typedef std::list <Action *> ListAction;


Mais ce cas n'a rien à voir. Tu ne regardes pas si les éléments de
la liste (des pointeurs) sont désalloués, mais si le destructeur des
objets qu'ils pointent est appelé.

Ce comportement est normal, std::list<> n'utilise pas l'opérateur
delete sur ses éléments, fussent-ils des pointeurs.

--drkm

1 2