Stringlisten freigeben

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Stringlisten freigeben

Beitrag von Aliobaba »

Hallo,

ich habe gelernt, dass man Stringlisten, die man in einer Prozedur oder in einer Funktion anlegt, am Ende der Funktion wieder freigeben soll.
Beispiel:

Code: Alles auswählen

Function Beispielfunktion ( Start_Folder : string) : TStringList;
var
  SL_File , SL_File_2 : TStringlist;
begin
 SL_File := TStringList.Create;
 SL_File.clear;
 SL_File_2 := TStringList.Create;
 SL_File_2.clear;

 // mach irgendwas...

 Result := SL_File_2;
 SL_File.Free;
 //SL_File_2.Free; // Wann wird das wieder frei gegeben?
end;         
Wenn ich die hier gebildete Stringliste aber in dem Code, der diese Funktion aufruft zum Beispiel an ein Memofeld übergebe,

Code: Alles auswählen

Mm_Playlisten.Lines := Globl.Beispielfunktion ( Basic_Folder ); 
dann darf ich die übergebene Stringliste aber nicht in der Function frei geben, da sonst ein "SIGSEGV"-Fehler auftritt.

Dieser Code im Abschnitt, der diese Funktion aufruft wird vom Compiler akzeptiert:

Code: Alles auswählen

Mm_Playlisten.Lines := Globl.Beispielfunktion ( 'xy' );
Globl.Beispielfunktion ( 'xy' ).Free;
Das ist mir nicht ganz verständlich:
Die Stringliste ist doch schon in der Funktion an "Result" übergeben! Warum beschwert sich der Compiler?
Ist die Freigabe der Stringliste außerhalb der Funktion gut/nötig/unsinnig?

Gruß Aliobaba
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Stringlisten freigeben

Beitrag von theo »

Ganz kann ich nicht folgen, was du genau gemacht hast.

Aber: Result ist nicht wie "return" in C-Sprachen.
Result springt nicht aus der Funktion raus, sondern bestimmt nur "schon mal" den Rückgabewert.
Danach geht es weiter in der Funktion und wenn dort das Result freigegeben wird, ist es nachfolgend natürlich nicht mehr verfügbar.
D.h. die Funktion gibt nicht dann etwas zurück, wenn du Result:= setzt, sondern dann, wenn sie abgearbeitet ist.

Schicker ist mMn, das Objekt auf der gleichen Ebene freizugeben, wo es auch erstellt wurde, also es der Funktion als Parameter mitzugeben.

Vllt. habe ich dein Problem aber auch falsch verstanden.

Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Re: Stringlisten freigeben

Beitrag von Aliobaba »

Hallo Theo,
nein, Du hast mich genau richtig verstanden.
Und ich Deine Antwort auch! Vielen Dank!!
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Stringlisten freigeben

Beitrag von fliegermichl »

Die Stringliste muß schon wieder freigegeben werden. Das kann aber nicht in der Funktion passieren, weil dann natürlich das Funktionsergebnis ungültig ist.

Eleganter wäre es, die Stringliste vor dem Aufruf der Funktion zu erzeugen und nach der Verwendung wieder frei zu geben.

Code: Alles auswählen

procedure MachwasMitStringlist(sl : TStringlist);
begin
 sl.Add('Eine Zeile');
end;

var sl : TStringlist;
begin
 sl := TStringlist.Create;
 MachwasMitStringlist(sl);
 memo1.Lines := sl;
 sl.Free;
end.

Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Re: Stringlisten freigeben

Beitrag von Aliobaba »

Hallo,
in diesem Zusammenhang einmal eine ganz grundsätzliche Frage (eigentlich einige Fragen, die aber zusammen gehören):
(Der Name diese Rubrik "Einsteigerfragen" gibt mir dazu den nötigen Mut)

1. Was passiert eigentlich, wenn man am Ende einer Prozedur ein (z.B.) StringList nicht frei gibt?
2. Ich glaube, ich habe verstanden, dass dann wohl kleine Adressbereiche im Speicher ungenutzt als "garbage" besetzt bleiben?
3. Ist das bei den heutigen Speichergrößen noch ein reelles Problem?
4. oder eher eine "kosmetisch bedingte" Übereinkunft im Kapitel "Schöner programmieren"?
(so wie das verpönte "goto"[ist manchmal nämlich die einfachste Lösung])
5. Ich vermute, dass, wenn eine StringListe, die innerhalb einer Funktion/Prozedurdeklariert worden ist, nicht frei gegeben wird, diese in einer anderen Prozedur/Funktion auch dann einen neuen Speicherbereich beansprucht, wenn diese den selben Namen hat.
6. Müsste man dann konsequenterweise nicht auch jeden "String" oder jeden "Integer"-Wert, der in Prozeduren oder Funktionen deklariert wird, auch frei geben?

So. -- Das waren jetzt Fragen, die ich mir schon immer mal gestellt habe, mich aber noch nie getraut habe zu fragen

Gruß Aliobaba
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Stringlisten freigeben

Beitrag von theo »

Hast du schon mal etwas von Stack und Heap gehört?
Z.B. hier
https://stuckismus.de/understanding-mem ... in-delphi/

Benutzeravatar
kupferstecher
Beiträge: 422
Registriert: Do 17. Nov 2016, 11:52

Re: Stringlisten freigeben

Beitrag von kupferstecher »

Aliobaba hat geschrieben:
Do 27. Jan 2022, 14:02
1. Was passiert eigentlich, wenn man am Ende einer Prozedur ein (z.B.) StringList nicht frei gibt?
2. Ich glaube, ich habe verstanden, dass dann wohl kleine Adressbereiche im Speicher ungenutzt als "garbage" besetzt bleiben?
Bei einer StringList handelt es sich ja um eine Klasseninstanz, die Variable ist nur eine Referenz auf den Speicherblock, wo die eigentliche Instanz liegt (Also ein Pointer aber in anderer Gestalt). Deshalb kann man Klasseninstanzen zuweisen und kopiert die Daten dabei nicht.
Wenn jetzt die Variable selbst, also die Referenz, nicht mehr vorhanden ist, weil sie nur lokal war und nicht auf irgend eine Art übergeben wurde, ist der Speicherblock nach wie vor vorhanden, das Programm hat aber keinen Zugriff mehr. -> Speicherleck
3. Ist das bei den heutigen Speichergrößen noch ein reelles Problem?
Das wird dann zum handfesten Problem, wenn die Funktion oft aufgerufen wird und das Programm längere Zeit läuft.
4. oder eher eine "kosmetisch bedingte" Übereinkunft im Kapitel "Schöner programmieren"?
Für einmalig gebrauchten Speicher, der ja dann im "Verbrauch" überschaubar ist, trifft das zu. Allerdings, um echte kritische Speicherlecks zu erkennen gibt es den HeapTrace. Der beim Beenden vom Programm alle nicht freigegebenen Variablen auflistet. Hat man da zig Blöcke drin, bei denen man sagt das bisschen Speicher macht ja nichts, dann ist der HeapTrace kaum zu gebrauchen. Also ja, ein sauberes Programm räumt den Speicher komplett auf.

Bei einem Quick-und-Dirty-Progrämmchen seh ich es nicht so kritisch, beim Beenden vom Programm räumt das Betriebssystem letztlich auf.
5. Ich vermute, dass, wenn eine StringListe, die innerhalb einer Funktion/Prozedurdeklariert worden ist, nicht frei gegeben wird, diese in einer anderen Prozedur/Funktion auch dann einen neuen Speicherbereich beansprucht, wenn diese den selben Namen hat.
Ja. Und wenn die Funktion öfters aufgerufen wird, wird eben jedes Mal von Neuem Speicher reserviert.
6. Müsste man dann konsequenterweise nicht auch jeden "String" oder jeden "Integer"-Wert, der in Prozeduren oder Funktionen deklariert wird, auch frei geben?
Globale Variablen existieren die ganze Zeit, solange das Programm läuft, der Compiler kümmert sich darum (Statischer Speicher). Lokale Variablen liegen auf dem Stack und existieren nur solange die Funktion durchlaufen wird. Der Compiler fügt am Anfang der Funktion Code ein, der den Speicher für die Variablen auf dem Stack reserviert (Stack Frame) und fügt am Ende der Funktion Code ein, der den Speicher wieder frei gibt. Solche normale Variablen "leben" also nur entweder über die ganze Programmlaufzeit oder über die Laufzeit einer Funktion/Prozedur.
Klasseninstanzen werden im dynamischen Speicher abgelegt. Und zwar wird er beim Aufruf des Konstruktors reserviert. Die Variable selbst, also die Referenz auf den Speicherblock hat die Lebenszeit wie eine 'normale' Variable.

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

Re: Stringlisten freigeben

Beitrag von fliegermichl »

Auf den ersten Blick scheint der Stack nicht unbedingt der geeignetste Ort für Parameter und lokale Variablen zu sein.
Bei genauerer Betrachtung ist er es doch.

Bevor der Compiler eine Funktion aufruft, schiebt er deren Parameter auf den Stack (oder wahlweise in Prozessor Register).
Dann wird die Funktion aufgerufen.
Der Initialisierungscode der Funktion schafft Platz für die lokalen Variablen und beginnt dann mit der Ausführung der Funktion.

Ist die Funktion beendet, wird der Platz für die lokalen Variablen und Parameter wieder freigemacht und zum Aufrufer zurückgekehrt.
Die lokalen Variablen und Parameter "leben" eben genau so lange wie die Funktion.

Wenn Du eine lokale Variable sl : TStringlist deklarierst, dann reserviert der Compiler hier den Speicherplatz für einen Zeiger!
sl := TStringlist.Create sorgt jetzt dafür, daß der Platz für die Instanz der Stringlist auf dem Heapspeicher reserviert wird und speichert dessen Adresse in der lokalen Variable.

Nach dem verlassen der Funktion wird zwar der Platz, der für den Zeiger reserviert wurde aufgeräumt, aber nicht der im Heap reservierte Speicher.
Genau genommen passiert das gleiche auch bei der Verwendung von Strings. Hier sorgt aber der Compiler automatisch für die Freigabe des Speichers auf dem Heap.

Wenn Deine Funktion kein Funktionsergebnis vom Typ TStringlist hätte, dann wäre die Information flöten gegangen wo die Instanz angelegt wurde.

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

Re: Stringlisten freigeben

Beitrag von Warf »

Zu den oben genannten sachen noch hinzuzufügen ist, Speicherlecks können sogar gewollt sein. Use-After-Free bezeichnet eine Form von Bug bei der ein Speicherberech benutzt wird nachdem er gefreed wurde. Freier Speicher kann wiederverwendet werden, also kann man damit auf sachen zugreifen auf die man nicht zugreifen können sollte:

Code: Alles auswählen

var
  sl1, sl2: TStringList;
begin
  sl1 := TStringList.Create;
  sl1.Free;
  sl2 := TStringList.Create; // überschreibt den jetzt frei gewordenen speicher von SL1
  sl1.Text := 'Hallo Welt!';
  WriteLn(sl2.Text); //Hallo Welt!
end;
Wie du sehen kannst kann man über sl1 sl2 bearbeiten obwohl es eigentlich nicht sein sollte. Und nein, FreeAndNil hilft nicht wirklich, denn es kann mehrere Referenzen auf die gleiche klasse geben:

Code: Alles auswählen

var
  sl1, sl2, sl3: TStringList;
begin
  sl1 := TStringList.Create;
  sl2 := sl1;
  FreeAndNil(sl1);
  sl3 := TStringList.Create; // überschreibt den jetzt frei gewordenen speicher von SL1
  sl2.Text := 'Hallo Welt!';
  WriteLn(sl3.Text); //Hallo Welt!
end;
Um das problem zu lösen müsste man halt alle referenzen auf sl1 kennen und diese nil setzen, und dann hat man praktisch einen Garbage Collector (oder referenzzählung) und dann kann man die ganze Übung mit Speicherfreigabe sowieso automatisieren.

Um dieses Problem zu lösen tuen Sicherheitskritische Funktionen gerne einfach kein Free ausführen. Use-After-Free ist gelöst wenn man einfach nie Free aufruft. Das lässt zwar Speicherlecks, aber dafür eliminierst du alle use-after-free bugs.

Zum anderen hast du nach strings gefragt. Tatsächlich leben Strings (und arrays) sowie Klassen auf dem Heap, aber der Compiler baut automatisch code zum checken ob die noch verwendet werden und automatischen freigeben. Also eigentlich müsste man gar kein Manuelles Free aufrufen, der Compiler könnte das theoretisch alles automatisieren, aber die Delphi Entwickler dachten sich damals das das überall .Free aufzurufen so viel Spaß macht das man bei Klassen das dem Programmierer doch nicht nehmen will, und deshalb müssen wir bis heute damit leben.

Oberon z.B. Wirths neuste sprache hat nen Garbage collector, und Wirth hat damit ein gesammtes Betriebsystem geschrieben. Es gibt eigentlich keinen grund Manuelles Free aufrufen zu müssen.

Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Re: Stringlisten freigeben

Beitrag von Aliobaba »

Also eigentlich müsste man gar kein Manuelles Free aufrufen, der Compiler könnte das theoretisch alles automatisieren, aber die Delphi Entwickler dachten sich damals das das überall .Free aufzurufen so viel Spaß macht das man bei Klassen das dem Programmierer doch nicht nehmen will, und deshalb müssen wir bis heute damit leben.
:lol: :lol:

Hallo,
vielen herzlichen Dank für diese tollen Antworten!
Und ich habe schon befürchtet, ich bekomme den Hinweis:
"Ach, Jungchen, lies doch erstmal ein bisschen in dem Buch "Delphi-Grundlagen Erste Hilfe für Dummies"
- und ja: Ich hätte bei dieser Frage durchaus Verständnis für eine solche Antwort gehabt!

Es beruhigt mich, dass auch Lazarus-Profis dieses Thema interessant finden:
viewtopic.php?f=55&t=14095

Schönes Wochenende!
Aliobaba
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6209
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Stringlisten freigeben

Beitrag von af0815 »

Aliobaba hat geschrieben:
Fr 28. Jan 2022, 08:40
Und ich habe schon befürchtet, ich bekomme den Hinweis:
Immer bei der Entwicklung HeapTrace einschalten. :D

SCNR :mrgreen:
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

hum4n0id3
Beiträge: 301
Registriert: So 5. Mai 2019, 15:23

Re: Stringlisten freigeben

Beitrag von hum4n0id3 »

Auch von mir ein herzliches Danke! Ich dachte auch bisher das Result dem return anderer Sprachen entspricht. Wieder was gelernt.

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

Re: Stringlisten freigeben

Beitrag von fliegermichl »

hum4n0id3 hat geschrieben:
Fr 28. Jan 2022, 10:46
Auch von mir ein herzliches Danke! Ich dachte auch bisher das Result dem return anderer Sprachen entspricht. Wieder was gelernt.
Das ist der exit Befehl

PascalDragon
Beiträge: 830
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: Stringlisten freigeben

Beitrag von PascalDragon »

Warf hat geschrieben:
Do 27. Jan 2022, 15:03
Zu den oben genannten sachen noch hinzuzufügen ist, Speicherlecks können sogar gewollt sein. Use-After-Free bezeichnet eine Form von Bug bei der ein Speicherberech benutzt wird nachdem er gefreed wurde. Freier Speicher kann wiederverwendet werden, also kann man damit auf sachen zugreifen auf die man nicht zugreifen können sollte:

Code: Alles auswählen

var
  sl1, sl2: TStringList;
begin
  sl1 := TStringList.Create;
  sl1.Free;
  sl2 := TStringList.Create; // überschreibt den jetzt frei gewordenen speicher von SL1
  sl1.Text := 'Hallo Welt!';
  WriteLn(sl2.Text); //Hallo Welt!
end;
Wie du sehen kannst kann man über sl1 sl2 bearbeiten obwohl es eigentlich nicht sein sollte.
Wobei das ein Implementierungsdetail des Heap Managers von FPC ist. Der könnte theoretisch mehrere Pages vorhalten und dann im Round Robin Verfahren belegen, dann würde dein Beispiel nicht mehr funktionieren. ;)
FPC Compiler Entwickler

Antworten