Memoryleaks

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
FrankS
Beiträge: 15
Registriert: Mi 26. Jan 2022, 18:55

Memoryleaks

Beitrag von FrankS »

Hallo ich wieder,
ich habe jetzt die Umsetzung eines C# Programms (Textadventure-Framework) nach FPC fertig und es läuft soweit. Allerdings habe ich große Probleme mit dem verhindern von memoryleaks beim beenden des Programms. Ich verstehe nicht so recht wie diese zustande kommen. :oops:
Jetzt zu meiner unverschämten Bitte: Vielleicht kann sich mal jemand mein Programm anschauen und mir hilfreiche Hinweise geben.
Zum testen einfach das Programm starten und test eingeben, damit wird eine Komandotestliste abgearbeitet.

Im Anhang mein Projekt inklusive dem C#-Projekt.

Gruß
Frank
Dateianhänge
PascalBiff.zip
(74.67 KiB) 18-mal heruntergeladen

Benutzeravatar
Zvoni
Beiträge: 533
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Memoryleaks

Beitrag von Zvoni »

Bin jetzt nicht durch alles durch, aber mir sind sofort zwei Dinge in GameDataUnit aufgefallen.

1) Ich war der Meinung, dass man einen Destructor "overriden" soll. hab jetzt schon die erste Stelle gefunden, wo das Schlüsselwort "override" in der Klassen-Definition fehlt
2) Du rufst FIrgendwas.Destroy auf --> Soll man nicht machen. Benutze FIrgendwas.Free

EDIT: AUTSCH!!!!
gerade gesehen in "class procedure TGameData.InitFObs();"

Code: Alles auswählen

var
    aStringList: TStringList;
begin
    FObs := TObsList.Create;

    FObs.Add(OB_Acorn,     TThing.Create('acorn', 'ordinary-looking acorn'));
    FObs.Add(OB_Bed,       TThing.Create('bed', 'nasty, rather damp bed', false, true));
    FObs.Add(OB_Bin,       TContainerThing.Create('bin', 'smelly old bin', Mass_MEDIUM, true, true, true, true));
        aStringList := TStringList.Create(); aStringList.CommaText := 'smelly,old';
        FObs.KeyData[OB_Bin].Adjectives := aStringList;
    FObs.Add(OB_Bone,      TThing.Create('bone', 'bone that looks as though it has been gnawed by a dog'));
    FObs.Add(OB_BrassKey,  TThing.Create('key', 'small brass key'));
        aStringList := TStringList.Create(); aStringList.CommaText := 'small,brass';
        FObs.KeyData[OB_BrassKey].Adjectives := aStringList;
//        ......
aStringList wird created, einer Eigenschaft zugewiesen, dann NOCHMAL created (und das kommt noch mehrmals vor)?!?!?!?
Zumal die Eigenschaft "Adjectives" ja anscheinend ein TStringList/TStrings ist. Wieso nicht direkt diese Eigenschaft createn?

Desweiteren Createst du TThing innerhalb der Add-Funktion. Dir ist klar, dass du dann keinerlei Referenz mehr darauf hast???
Zumal anscheinend FObs keinen Parent bekommt.

Alles gesagt: Diesen Code würde ich nichtmal mit der Beisszange anfassen
Zuletzt geändert von Zvoni am Fr 30. Jan 2026, 12:25, insgesamt 1-mal geändert.
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

Benutzeravatar
Zvoni
Beiträge: 533
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Memoryleaks

Beitrag von Zvoni »

Also wenn du dennoch das irgendwie so lassen willst, würde ich wie folgt vorgehen.
Und das ist nur die erste "Änderung".

UNGETESTET.
Bin mir jetzt nicht sicher, ob "Adjectives" automatisch created wird

Code: Alles auswählen

var
    aStringList: TStringList;
begin
    FObs := TObsList.Create;

    aStringList := TStringList.Create(); //<== Einmal createn

    FObs.Add(OB_Acorn,     TThing.Create('acorn', 'ordinary-looking acorn'));
    FObs.Add(OB_Bed,       TThing.Create('bed', 'nasty, rather damp bed', false, true));
    FObs.Add(OB_Bin,       TContainerThing.Create('bin', 'smelly old bin', Mass_MEDIUM, true, true, true, true));

        aStringList.CommaText := 'smelly,old';  //<==
        FObs.KeyData[OB_Bin].Adjectives.AddStrings(aStringList, True);  //<==

    FObs.Add(OB_Bone,      TThing.Create('bone', 'bone that looks as though it has been gnawed by a dog'));
    FObs.Add(OB_BrassKey,  TThing.Create('key', 'small brass key'));

        aStringList.CommaText := 'small,brass';
        FObs.KeyData[OB_BrassKey].Adjectives.AddStrings(aStringList, True);   //<==
//        ......
    aStringList.Free;  //Zum Schluss freigeben
End;    
Anstatt der "aStringList" vielleicht sogar direkt den overload von AddStrings verwenden, und die Strings direkt übergeben
https://www.freepascal.org/docs-html/rt ... rings.html

Code: Alles auswählen

FObs.KeyData[OB_BrassKey].Adjectives.AddStrings(['small','brass'],True);   //<==
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

FrankS
Beiträge: 15
Registriert: Mi 26. Jan 2022, 18:55

Re: Memoryleaks

Beitrag von FrankS »

Hallo Zvoni,
vielen Dank für deine Hinweise.

Ich habe erstmal unbedarft mehr oder weniger die Struktur des C#-Programms übernommen.

Ich dachte und so habe ich es auch getestet (glaube ich wenigstens), daß TFPGMap<TOb,TThing>.Add ein Objekt mit dem Key TOb und einer Kopie der Reference von TThing anlegt.

Mein Problem ist auch, dass ich im erzeugten dump der memoryleaks nicht sehen kann das hier ein Problem vorliegen soll.

Frank

Benutzeravatar
Zvoni
Beiträge: 533
Registriert: Fr 5. Jul 2024, 08:26
OS, Lazarus, FPC: Windoof 10 Pro (Laz/FPC fixes)
CPU-Target: 64Bit
Wohnort: BW

Re: Memoryleaks

Beitrag von Zvoni »

Hallo Frank,

wie gesagt: Das alles war nur der erste Blick in einer Unit.
Es war keine Aussage darüber, dass dies die Ursache für die Speicher-Lecks sind.
Ein System sie alle zu knechten, ein Code sie alle zu finden,
Eine IDE sie ins Dunkel zu treiben, und an das Framework ewig zu binden,
Im Lande Redmond, wo die Windows drohn.

FrankS
Beiträge: 15
Registriert: Mi 26. Jan 2022, 18:55

Re: Memoryleaks

Beitrag von FrankS »

Hallo,
ich spare mir mal einen neuen Thread.
Ich habe alle Speicherlecks eliminiert und eine Save- und Loadroutine implementiert.
Läuft soweit auch (dachte ich).
Beim programmieren habe ich einige writeLn Komandos in der Procedure verteilt. Als ich fertig war habe ich diese entfernt und plötzlich gibt es beim Beenden des Programms wieder Speicherlecks.
Es ist völlig egal wieviele Textausgaben und wo diese in der Procedure stehen, sobald eine dieser Textausgaben eine bestimmte Länge über oder unterschreitet gibt es diese Speicherlecks.
Mindestens eine muss in der richtigen Länge vorhanden sein.

Von den Textausgaben mit Kommentar reicht jeweils eine um die Speicherlecks zu erzeugen.

Was macht writeLn?
Ich kann mir einfach das Problem nicht vorstellen.

Code: Alles auswählen

class procedure TGameData.LoadGameData(fs: TFileStream);
var
    objID: TOb;
    roomID: TRm;
    currentClassName: shortstring;
begin
    CleanFObs;
    CleanFMap;
    Player.Free;

    writeLn ('                                                ');
    writeLn ('                                                ');
    writeLn ('                                                ');
    writeLn ('          ');                                       // Erzeugt das Problem
    writeLn ('                                                ');
    writeLn ('                                                ');
    writeLn ('                                                ');
    writeLn ('                                                                                          ');  // Erzeugt das Problem
    writeLn ('                                                ');

    // --- Die Objekte  -----------------------------------------
    for objID in TOb do begin
        fs.ReadBuffer(currentClassName, sizeof(currentClassName));
        case currentClassName of
            'TThing':               FObs.Add(objID,TThing.CreateFromStream(fs));
            'TGenericThing':        FObs.Add(objID,TGenericThing.CreateFromStream(fs));
            'TInteractiveActor':    FObs.Add(objID,TInteractiveActor.CreateFromStream(fs));
            'TContainerThing':      FObs.Add(objID,TContainerThing.CreateFromStream(fs));
            'TLockableThing':       FObs.Add(objID,TLockableThing.CreateFromStream(fs));
        else
            writeLn('ERROR NOT HANDLED TObsList Object ---> ', currentClassName);
        end;
    end;

    writeLn ('                                                ');
    writeLn ('          ');                                       // Erzeugt das Problem
    writeLn ('                                                ');


    // --- Die Räume  -------------------------------------------
    for roomID in TRm do begin
        if roomID <> RM_NOEXIT then begin
            fs.ReadBuffer(currentClassName, sizeof(currentClassName));
            FMap.Add(roomID,TRoom.CreateFromStream(fs));
		end;
	end;

    // --- Der Player  ------------------------------------------
    fs.ReadBuffer(currentClassName, sizeof(currentClassName));
    FPlayer := TActor.CreateFromStream(fs);

    // --- Die Referenzen  --------------------------------------
    CorrectReferences();

end;
Gruß
Frank

FrankS
Beiträge: 15
Registriert: Mi 26. Jan 2022, 18:55

Re: Memoryleaks

Beitrag von FrankS »

Hallo,
habe soeben festgestellt, daß ich speichern und laden mit der selben exe ausführen muß.
Dann läuft es.
Also suchen warum dem so ist.

Frank

Warf
Beiträge: 2254
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Memoryleaks

Beitrag von Warf »

Zvoni hat geschrieben: Fr 30. Jan 2026, 11:57 2) Du rufst FIrgendwas.Destroy auf --> Soll man nicht machen. Benutze FIrgendwas.Free
Nein!

Free basiert auf der Annahme das es bevorzugt ist das ein Programm in einem Undefinierten zustand weiter läuft und potentiell Daten zerstört als das es sauber mit einem zurückverfolgbaren stack trace crasht. Crashes sind zwar unschön, aber wenn das Programm in einem Status ist in dem es nicht sein sollte ist die alternative das es dinge tut die es nicht soll. Deshalb crashen unbehandelte Exceptions Programme auch für gewöhnlich und warum Sprachen wie Rust die zwar keine Exceptions haben trotzdem eine "panic" haben für undefinierten status der einfach das Programm mit stack trace crasht

Du solltest Free eigentlich nur verwenden wenn:
1. Du dir sicher bist das du bei jedem Destruktoraufruf die variable auf nil setzt
2. In einer situation bist wo nicht klar ist ob das objekt schon gefreed wurde oder noch existiert

In allen anderen Fällen versteckt Free einfach nur use-after-free Fehler die sonst potentiell durch testing gefunden würden. Ich hab mir die Nutzung von Free leider auch angewöhnt und kann die Muscle memory nicht einfach ablegen und Nutz es deshalb selbst zu oft. Aber das heißt nicht das man schlechte Eigenschaften weiterempfehlen sollte

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1742
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Memoryleaks

Beitrag von fliegermichl »

Warf hat geschrieben: Sa 7. Feb 2026, 12:20 ...
Ich hab mir die Nutzung von Free leider auch angewöhnt und kann die Muscle memory nicht einfach ablegen und Nutz es deshalb selbst zu oft. Aber das heißt nicht das man schlechte Eigenschaften weiterempfehlen sollte
Und was wäre dann die zu bevorzugende Variante?

Benutzeravatar
theo
Beiträge: 11153
Registriert: Mo 11. Sep 2006, 19:01

Re: Memoryleaks

Beitrag von theo »

fliegermichl hat geschrieben: Sa 7. Feb 2026, 17:30 Und was wäre dann die zu bevorzugende Variante?
Z.B. https://wiki.freepascal.org/FreeAndNil

PascalDragon
Beiträge: 1024
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Memoryleaks

Beitrag von PascalDragon »

Free ist die bevorzugte Variante unabhängig davon was Warf sagt und FreeAndNil ist auch kein Allheilmittel.
FPC Compiler Entwickler

Antworten