tomatoman schreef op vrijdag 01 april 2005 @ 02:03:
[...]
... en terwijl ik dit zit te typen constateer ik dat een falende aanroep van FunctieDieStringListUitPoept per definitie een geheugenlek kan veroorzaken. Stel dat FunctieDieStringListUitPoept er als volgt uitziet:
Wat je ook doet met exception blocks rond code die FunctieDieStringListUitPoept aanroept, een geheugenlek als gevolg van een TStringList die nooit meer wordt vernietigd is niet te voorkomen.
Helemaal waar, natuurlijk. Op de inhoud van de functie was ik verder nog niet ingegaan, maar dat zal ik nu doen.
CyeZ schreef op vrijdag 01 april 2005 @ 05:04:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
| function FunctieDieStringListUitPoept: TStrings;
begin
Result := TStringList.Create;
try
Result.Strings[-1] := 'Dit gaat niet goed'; // veroorzaakt een exception
except
if Assigned(Result) then begin
Result.Free;
end;
end;
end; |
Bijna goed. De test op Assigned(Result) is eigenlijk niet nodig (is toch altijd waar), en de exception verdwijnt nu, dus weet de aanroeper niet meer dat er iets mis ging (hij krijgt zelfs een dangling pointer terug -- de waarde in Result is nu bogus -- dus crasht met een AccessViolation zodra hij het object probeert aan te spreken).
Delphi:
1
2
3
4
5
6
7
8
9
10
| function FunctieDieStringListUitPoep : TStrings;
begin
Result := TStringList.Create;
try
// ... code die een exception kan geven
except
Result.Free;
raise;
end;
end; |
De bedoeling is natuurlijk dat het alloceren van de stringlist in FunctieDieStringListUitPoept, bij gebrek aan een beter woord, "atomair" plaatsvindt
[1]. Dat wil zeggen dat die functie wel de garantie moet geven dat als hij
• zonder exception afloopt, de stringlist gealloceerd is; en als hij
• met exception afloopt, de stringlist
niet gealloceerd is.
En het werkt op precies dezelfde manier als de functie bijvoorbeeld meerdere resources tegelijk moet alloceren: aan het eind van de functie zijn
alle resources tegelijk en succesvol gealloceerd, of als er iets mis ging (exception) zijn ze allemaal
niet gealloceerd (of weer vrijgegeven).
Hierbij treedt iets op wat ik "assumption of responsibility" zou willen noemen: op elk moment in de levensduur van een resource (object/stuk geheugen/filehandle, whatever), heeft één routine de verantwoordelijk voor deze resource. Zolang deze routine verantwoordelijk is, dient hij ervoor te zorgen dat de resource niet gelekt wordt, maar vrijgegeven wordt wanneer dat toepasselijk is. Een routine kan wel verantwoordelijkheid over het object overdragen, door het aan een andere routine over te dragen -- middels een aanroep naar een andere routine, of door het teruggeven van de resource als functieresultaat. Daarna dient de volgende routine in de ketting dus te zorgen dat de resource tzt. weer vrijgegeven wordt. (Hierbij dient de aangeroepen routine natuurlijk wel te wéten dat hij verantwoordelijk wordt, anders is er geen routine meer verantwoordelijk en zal de resource onherroepelijk lekken. De aangeroepen routine moet dus de verantwoordelijkheid
aannemen).
Op deze manier heb je duidelijkheid welke resource door wie vrij moet worden gegeven, en voorkom je geheugenlekken.
[1] Niet echt de goede term; heeft iemand een betere...?