TObjectList , Remove

Résolu
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 - 28 janv. 2009 à 13:41
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 - 3 févr. 2009 à 09:07
Bonjour ,
J'ai un gros problème de libération de mémoire et je ne vois pas ou est mon erreur ...

J'ai une structure de ce type :

type
  TMyObject  = class
 private
 public
 constructor Create ;
 destructor Destroy ;
end;

  TMyList = class(TObjectList)
  protected
    function Get( Index : Integer ) : TMyObject;
    procedure Put( Index : Integer; Item : TMyObject);
  public
    property Items[ Index : Integer ] : TMyObject read Get  write Put; default;
    constructor Create;
  end;

 TAutreObject = class
 private
 MyList : TMyList;
// blabalbla
public
 construtor Create ;
 procedure RemoveAll;
...

implementation
{ - TMyObject ---------------------------------------------------------- }
constructor TMyObject .Create
begin
 inherited Create;
end;

destructor TMyObject .Destroy
begin
 // je supprime bien sur ts ce que j'ai pu creer ici, mais pr l'exemple, ca n'en vaut pas la peine ...
 inherited Destroy;
end;

 
{ - TMyList ---------------------------------------------------------- }
constructor TMyList .Create;
begin
  inherited Create(True);// OwnObject est a True, Donc libération des Objects Automatique !
end;

function TMyList .Get( Index : Integer ) : TMyObject;
begin
  Result := inherited Get( Index );
end;

procedure TMyList .Put( Index : Integer; Item : TMyObject);
begin
  inherited Put( Index, Item );
end;

{ - TAutreObject ---------------------------------------------------------- }
construtor TAutreObject .Create ;
begin
 inherited Create;
MyList  := TMyList.Create;
// blablabla
end;

procedure TAutreObject .RemoveAll;
var
 i : integer;
begin
 for i:=0 to MyList.Count-1 do begin
   //Supprime un objet spécifié de la liste
  
// et (si OwnsObjects est à true) libère l'objet.
   MyList.Rmove(MyList.Items[i]);
 end;

 MyList.Capacity := MyList.Count;
// Mais il ne supprime rien ! Enfin si , mon vecteur est vide
//mais ma mémoire est toujours autant occupée , prq ?!!
end;

Voila ben ma question est simple , Pourquoi ca ne me libère rien coté mémoire ?!

Merci

31 réponses

Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
2 févr. 2009 à 18:05
Bon ça y est ,
j'ai résolu mon problème et attention c'était encore plus con que tous , et ça prouve qu'il faut toujours faire gaffe au(x) message(s) d'erreur(s)

J'avais ceci mais je ne faisait jamais très attention :
[Avertissement]  La méthode 'Destroy' cache la méthode virtuelle du type de base 'TObject'

Hé oui , tt simplement oublié destructor Destroy;override
; !

Merci à tous quand même

Nico
3
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
28 janv. 2009 à 13:45
Bien sur pour ajouter qqch à ma liste je fais comme ceci :

procedure  TAutreObject.AddObject ( Item : TMyObject ) ;
begin
 MyList.Add ( Item );
end;
0
WhiteHippo Messages postés 1154 Date d'inscription samedi 14 août 2004 Statut Membre Dernière intervention 5 avril 2012 3
28 janv. 2009 à 14:27
Bonjour

Une petite question : Pourquoi tu n'emploies pas tout simplement MyList.Clear pour supprimer les éléments ?

Cordialement.
<hr />"L'imagination est plus importante que le savoir." Albert Einstein
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
28 janv. 2009 à 14:36
Parce que ici je ne montre pas ts le code
(il est un peu long) ,
et que je ne dois pas effacer toute la liste , juste un certain nombre objet de la liste avec un Tag spécial ...
(Dans mon RemoveAll j'ai une condition , et si elle est a true -> Remove )

J'ai l'instruction  TAutreObject.Clear qui appele tous simplement MyList.Clear , mais la encore , ce n'est aps libéré !

(J'ouvre le gestionnaire de tâches en même tps, je créé +- 500 object , ma mémoire occupée grimpe et quand je fais un Clear ou un RemoveAll, pas de différences , mais pourtant mon vecteur est vide ... )
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
WhiteHippo Messages postés 1154 Date d'inscription samedi 14 août 2004 Statut Membre Dernière intervention 5 avril 2012 3
28 janv. 2009 à 14:45
Et avec un petit coup de MyList.Pack ?

N.B. Attention les objets contenus doivent avoir été assigné à NIL après leur suppression.

Cordialement.
<hr />"L'imagination est plus importante que le savoir." Albert Einstein
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
28 janv. 2009 à 15:08
Pack
Supprime tous les éléments nil (Delphi) ou NULL (C++) du tableau Items.

Description

La méthode Pack déplace tous les éléments non-nil (Delphi) ou non-NULL (C++) au début du tableau Items, puis réduit la propriété Count au nombre d'éléments réellement utilisés. Pack ne libère pas la mémoire utilisée pour stocker les pointeurs nil (Delphi) ou NULL (C++). Pour libérer la mémoire allouée aux entrées inutilisées qui sont supprimées par Pack, affectez la nouvelle valeur de Count à la propriété Capacity.

Donc j'ai fais ca :

procedure TAutreObject .RemoveAll;
var
 i : integer;
begin
 for i:=0 to MyList.Count-1 do begin
 MyList.Items[i] := nil;
 end;
 MyList.Pack;
 MyList.Capacity := MyList.Count;
end;

Ca ne change rien !

Et quand je veux faire

procedure TAutreObject .RemoveAll;

var

 i : integer;

begin

 for i:=0 to MyList.Count-1 do begin
 MyList.Items[i] .free;// Forcement ca plante royalement !

 end;

 MyList.Capacity := MyList.Count;

end;
0
WhiteHippo Messages postés 1154 Date d'inscription samedi 14 août 2004 Statut Membre Dernière intervention 5 avril 2012 3
28 janv. 2009 à 15:32
Moi j'aurais plutôt écrit quelquechose comme :

procedure TAutreObject .RemoveAll;
var
 i : integer;
begin
  for i:=0 to MyList.Count-1 do 
  begin
    MyList.Remove(MyList.Items[i]); 
    MyList.Items[i] := nil; // Assignation à nil après la libération.

  end;
  MyList.Pack;
  MyList.Capacity := MyList.Count;
end;

N.B. Désolé, j'ai pas de quoi testé pour l'instant, alors c'est tout en live !!!

Cordialement.
<hr />"L'imagination est plus importante que le savoir." Albert Einstein
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
28 janv. 2009 à 15:53
MyList.Remove(MyList.Items[i]);


MyList.Items[i] := nil; // Assignation à nil après la libération.

Je pense pas que ça marche , enfin ça plante même

Aide : "Après qu'un objet a été supprimé, tous les objets qui suivent sont déplacés et Count est décrémenté"

Donc dans ton code , le




MyList.Items[i]:=nil , l'indice "i" ne fait plus référence au même object , Si ?

Merci de ton aide
0
WhiteHippo Messages postés 1154 Date d'inscription samedi 14 août 2004 Statut Membre Dernière intervention 5 avril 2012 3
28 janv. 2009 à 16:41
Je ferais des tests ce soir, ça m'évitera peut etre de t'aiguiller vers des fausses pistes et/ou de dire ces betises.

Cordialement.
<hr />"L'imagination est plus importante que le savoir." Albert Einstein
0
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
28 janv. 2009 à 17:25
Salut Nicolas.

for i:=0 to MyList.Count-1 do
  begin
   //Supprime un objet spécifié de la liste
   // et (si OwnsObjects est à true) libère l'objet.
   MyList.Remove(MyList.Items[i]);
   asm DEC I end;
  end;

Faut utiliser l'asm sinon Delphi va grogner. Chez moi, 50000 éléments, application passe à 8000Ko mémoire, après RemoveAll, retombe à 4000.
La raisonest que quand tu supprime un objet, Count est automatiquement décrémenté : donc, tu te retrouves avec une belle V.A, et aucune libération.

Cordialement, Bacterius !
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
28 janv. 2009 à 21:43
Bacterius , utiliser l'asm comme ca , c'est totalement inutile

soit 1 fonction est pur asm soit elle est en pascal...
0
Bacterius Messages postés 3792 Date d'inscription samedi 22 décembre 2007 Statut Membre Dernière intervention 3 juin 2016 10
28 janv. 2009 à 21:51
Ok, je m'en souviendrai, ça pourrait peut-être m'aider ce conseil.
Bon ... sinon ça marche si on recule de 1 à chaque fois ?
Sinon tu peux partir de la fin, ça marche aussi et sans asm.

Cordialement, Bacterius !
0
cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
29 janv. 2009 à 14:16
Salut,

Il y a effectivement fuite de mémoire !

Et vous avez de la chance que f0xi ou autre ne soit pas encore passé là !
Ils vous renieraient en tant que padawan jusqu'à votre 57 ème génération.

Prenons un exemple simple en pseudo code.

liste[0] = tata;
liste[1] = titi;
liste[2] = toto;

for i:= 0 to liste.Count - 1 do
  liste.Remove(liste[i]);

Première boucle. liste.Count vaut 3, i vaut zéro :
liste.Remove(liste[0]);

[Aide de Delphi]
Appelez Remove pour supprimer un objet spécifique de la liste lorsque l'indice est inconnu. La valeur renvoyée est l'indice de l'objet dans le tableau Items avant qu'il ne soit supprimé. Si l'objet spécifié n'est pas trouvé dans la liste, Remove renvoie –1. Si OwnsObjects est à True, Remove libère l'objet en plus de le supprimer de la liste.

Après qu'un objet a été supprimé, tous les objets qui suivent sont déplacés et Count est décrémenté.
de Delphi

Donc le contenu de liste devient :
liste[0] = titi;

liste[1] = toto;

Bin oui, tata est libéré, titi et toto sont déplacés (Déplacement de pointeur, coûte pas grand chose).

Deuxième boucle. liste.Count vaut 2, i vaut 1 :
  liste.Remove(liste[1]);

contenu de liste après Remove :

liste[0] = titi;

et on sort de la boucle.

Bilan titi n'est pas libéré.

Il faut par exemple coder ça comme ça :

procedure TAutreObject .RemoveAll;
var
  nCount: Integer;
  i: Integer;
begin
  nCount:= MyList.Count;
 for i:= 0 to nCount - 1 do
   MyList.Remove(MyList.Items[0]);
end;

Remarquez le 0 : on supprime toujours le premier élément de la liste et les autres se décalent. Le deuxième (1) devient premier (0) et est supprimé au tour suivant. Remarquez aussi la récupération de count dans une vériable afin d'empècher la réévaluation.

Mais côté perf, c'est tout pourri. Il vaut bien mieux attaquer à la fin de la liste, pour éviter les décalages :

procedure TAutreObject .RemoveAll;

var

  i: Integer;

begin

 for i:= nCount - 1 downto 0 do

   MyList.Remove(MyList.Items[i]);

end;

Ainsi on supprime toujours le dernier élément.

Finalement, une petite remarque concernant Windows ou le gestionnaire des tâche qui dit que la mémoire n'est pas rendu. C'est vrai : Windows ne la pas vraiment récupéré, et ne peut pas la réaffecter à un autre processus.

C'est à cause des pages. La mémoire est divisée en pages de 4 ko. C'est l'unité de mémoire que Windows peut donner à un processus. Les allocations sont réalisées par VirtualAlloc. Après, par dessus ça, vient un tas, fourni par Windows (HeapAlloc, GlobalAlloc, LocalAlloc...) ou par le langage : Delphi propose certainement son propre tas.

En interne, le tas peut utiliser un autre tas, mais au final, c'est VirtualAlloc qui est utilisé et qui ne peut que délivrer 4 ko, rien, ou un multiple de 4ko.

Les tas subdivise les pages qui lui sont données. Supposons que l'on demande deux variables de 2 ko. Le tas peut demander une page de 4 ko, et stocker les deux dedans. Problème si une des deux variables est libérée, la page ne peut être rendu au système : la moitié est encore utilisée. Donc le processus consomme toujours 4 ko dans le gestionnaire des tâches, alors qu'il a libéré la moitié de sa mémoire ! Par contre, le tas sait que la moitié vide est vide, et peut la réutiliser pour des demandes ultérieures.

C'est ainsi que sur une application qui tourne depuis longtemps, un grand nombre de pages peut être bouffé par une ou deux variables qui restent dessus. Le processus à l'air donc de consommer beaucoup de mémoire, alors qu'en fait la plupart de sa mémoire allouée est vide.

L'utilisation de pages par le système n'est pas du tout spécifique à Windows, mais est dûe à l'architecture du processeur et du fait que la mémoire est virtualisée : chaque processus à son propre espace, et il faut tout traduire en adresse physique à chaque accès. Il y a des tables de traduction page virtuelle -> page physique utilisées par le processeur, bien que gérées par Windows.
0
cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
29 janv. 2009 à 14:18
Erratum, dans la bonne méthode, nCount non définit bien sûr :

procedure TAutreObject .RemoveAll;
var
  i: Integer;
begin
 for i:= MyList.Count - 1 downto 0 do
   MyList.Remove(MyList.Items[i]);
end;
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 janv. 2009 à 14:42
J'ai enfin compris le mystère du gestionnaire des tâches !

Merci  rt15
0
Nicolas___ Messages postés 992 Date d'inscription jeudi 2 novembre 2000 Statut Membre Dernière intervention 24 avril 2013 1
29 janv. 2009 à 14:54
Merci rt15 pr ts ces infos très utile
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 janv. 2009 à 15:04
@rt15
« Windows ne la pas vraiment récupéré, et ne peut pas la réaffecter à un autre processus »

- Imaginons une application qui doit créer de très nombreux objets et les libérer presque aussitôt. Si on n'a pas bcp de chance, ne risque t-on pas d'arriver à un plantage du système, même si le code est irréprochable du côté des libérations



- Existe t-il un moyen ou une méthode de codage pour se prémunir de ça
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 janv. 2009 à 15:18
... Une sorte ne défragmentation de la ram est-elle possible
 
(  J'espère ne pas abuser de ta patience, rt15. Qu'on sait infinie, mais quand même...  ;)
0
Caribensila Messages postés 2527 Date d'inscription jeudi 15 janvier 2004 Statut Membre Dernière intervention 16 octobre 2019 18
29 janv. 2009 à 15:24
... D'ailleurs, n'est-ce pas exactement le même phénomène au niveau du DD qui nous oblige à le défragmenter de temps en temps?

Allez! Je vais arrêter là (pour le moment).
 
0
cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
29 janv. 2009 à 15:49
Pour le parallèle avec le dur, je n'en sais rien, je ne sais pas comment ça fonctionne exactement.

Tu peux effectivement te retrouver avec un processus qui consomme beaucoup plus de mémoire qu'il n'en utilise vraiment, avec effectivement un genre de fragmentation.

Mais il n'y a aucun moyen de défragmenter en Delphi, car on ne peut travailler qu'au niveau page. Suppose que tu as obj1 dans page1 et obj2 dans page 2 tels que obj1 et obj2 puissent tenir dans la même page.

Tu voudrais mettre obj1 et obj2 dans page1 et rendre page2 au système. Tu aurais effectivement l'impression de défragmenter.

Le problème c'est que le programme à qui appartient obj2 "perd" obj2. Par exemple obj2 était dans une ObjectList. Une ObjectList est en gros un tableau de pointeurs sur les objets. Donc dans ton ObjectList, si tu as l'impression d'avoir obj1 et obj2, tu as un tableau avec deux adresse, celles d'obj1 et celle d'obj2.

Si tu déplace obj2 sans mettre à jour ton ObjectList, l'adresse contenue dans la liste pointera toujours sur la page mémoire libérée -> violation d'accès lors du prochain accès à obj2.

Le problème est plus profond que ça : il n'y a pas besoin d'ObjectList. Le langage machine est fondamentalement basés sur les adresses et n'arrête pas de les manipuler. Si des objets, structures ou variables sont déplacées, tout planterais.

Après, il existe des langages plus abstraits ou on manipule pas vraiment des pointeurs, mais plutôt des pointeurs sur des pointeurs. A ce moment là, on peut défragmenter la mémoire en corrigeant les pointeurs. C'est ce qui est fait en Java il me semble. Mais ce système n'est pas sans inconvénients, sur le plan des perfs notamment.

On peut imaginer des techniques et des structures de données pour limiter la fragmentation. Par exemple en utilisant des tableaux et en recopiant le dernier élément dans l'élément que l'on supprime. Le problème étant que l'ex dernier élément n'a plus le même indice dans le tableau, donc il faut gérer cela, etc...
0
Rejoignez-nous