TSringList als Funktions-Rückgabe sinnvoll?

Für Fragen von Einsteigern und Programmieranfängern...
wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von wp_xyz »

Winni hat geschrieben:
Do 27. Jan 2022, 15:04
Irre: Objecte müssen freigegeben werden. Ganz was Neues!
Es ist völlig klar, dass nach einem "L := TStringList.Create" ein "L.Free" folgen muss. Es ging mir eher um die versteckten Constructoren. Wenn ich einer Funktion mit unscheinbarem Namen etwas erzeuge und dort nicht wieder freigebe, stelle ich mir ein Bein, weil ich höchstwahrscheinlich übersehe, dass etwas erzeugt worden ist (auch wenn es im Nachhinein selbstverständliche ist).

Besonders kriminell wird es, wenn man den Rückgabewert einen Funktion unter den Tisch fallen lässt.

Wer würde vermuten, dass in einer so harmlosen Anweisung wie

Code: Alles auswählen

Procedure TForm1.ListFilesInDir(Directory: String);
begin
  ListView1.Items.Assign(FindAllFiles(Directory));
end;
ein Speicherleck erzeugt wird?

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Winni »

wp_xyz hat geschrieben:
Do 27. Jan 2022, 18:42

Besonders kriminell wird es, wenn man den Rückgabewert einen Funktion unter den Tisch fallen lässt.

Wer würde vermuten, dass in einer so harmlosen Anweisung wie

Code: Alles auswählen

Procedure TForm1.ListFilesInDir(Directory: String);
begin
  ListView1.Items.Assign(FindAllFiles(Directory));
end;
ein Speicherleck erzeugt wird?
Jo, das sind fiese Anfängerfallen.
Aber wie immer hilft Nachdenken.
Oder heaptrc


Winni

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

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von kupferstecher »

photor hat geschrieben:
Do 27. Jan 2022, 17:06
Oder mache ich das besser AUSSERHALB der Funktion, dann

Code: Alles auswählen

begin
  neueListe := TStringList.Create;
  neueListe := MeineFunktion(Data_List);
  // mache weiter mit neueListe
  ...
  neueListe.Free;
end;
Das funktioniert so hoffentlich nicht. neueListe ist ja das Funktionsergebnis und kein Parameter.

Allerdings heißt es in der Doku:
Remark: Function results are treated as pass-by-reference parameters. That is especially important for managed types: The function result may be non-nil on entry, and set to a valid instance of the type.
Ein kurzer Test hat zum Crash geführt (Segmentation Vault):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender:TObject);
begin
  Button1:= SetLblCaption;
end;

function TForm1.SetLblCaption:TButton;
begin
  if Result is TButton //<- SigSegV
  then Label1.Caption:= Result.Name;
end;
Vielleicht kann das ein PascalDragon kurz kommentieren :-)

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Winni »

kupferstecher hat geschrieben:
Do 27. Jan 2022, 22:19

Code: Alles auswählen

procedure TForm1.Button1Click(Sender:TObject);
begin
  Button1:= SetLblCaption;
end;

function TForm1.SetLblCaption:TButton;
begin
  if Result is TButton //<- SigSegV
  then Label1.Caption:= Result.Name;
end;
Vielleicht kann das ein PascalDragon kurz kommentieren :-)
Hi!

Du musst doch erstmal den Button erzeugen! Das Resultat von SetblCaption ist doch an diesem Punkt undefiniert, bestenfalls Nil - das weiss ich nicht.

Result ist doch nix anderes als eine Variable, deren Typ Du in der Definition des Funktions-Resultats definiertst.

Also:

Code: Alles auswählen

function TForm1.SetLblCaption:TButton;
begin
  Result :=  TButton.Create(Nil);
  result.Name := 'MarleneDietrich'; 
  Label1.Caption:= Result.Name;
end;
Winni

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

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Warf »

Wenn es um das zurückgeben von manuell gemanageten Ressourcen geht (worunter Klassen fallen, aber auch z.B. FileHandles) gibt es zwei Probleme, das erste ist das in diesem Fall der Rückgabewert immer beachtet werden muss. In (Object)Pascal ist es komplett legal den Funktionswert zu ignorieren, das würde dann natürlich zu datenverlust und damit in diesem Fall zu Speicherlecks führen:

Code: Alles auswählen

function Foo: TStringList;
...

begin
  Foo; // Erzeugt ein Speicherleck
end;
D.h. allein von dieser seite aus macht es mehr sinn das Ergebnis als zusätzlichen Parameter zu übergeben, da das den Nutzer zwingt es entgegen zu nehmen. Man sollte seinen Code immer so bauen das es so schwer wie Möglich ist damit was falsch zu machen, und hier ist ist ein extra Parameter ein weg den Syntaxcheck des Compilers zu nutzen um solche Fehler zu vermeiden.

Das zweite sind exceptions. Wenn eine Exception aufkommt sollte man davon ausgehen das das Ergebnis der Funktion nicht aussagekräftig ist, da du von außen, im allgemeinen ja nicht weißt wo die exception aufgetreten ist und in welchem State das Result zu diesem Zeitpunkt war. D.h. du kannst nicht sowas machen:

Code: Alles auswählen

try
  X := Foo;
finally
  X.Free;
end;
da X bei einer Exception undefiniert sein kann kann X.Free selbst einen Fehler auslösen. Sondern es muss so gemacht werden:

Code: Alles auswählen

X := Foo;
try
  ...
finally
  X.Free;
end;
So wie man das auch mit dem Konstruktor Create macht. Das Problem hierbei ist das wenn Foo eine Exception wirft nachdem das Result erzeugt wurde, muss es gefreed werden, und das muss innerhalb von Foo geschehen:

Code: Alles auswählen

function Foo: TStringList;
begin
  ...
  Result := TStringList.Create;
  try
  
  except
    Result.Free;
    raise;
  end;
end;
Das ist übrigens auch was der Compiler automatisch beim Konstructor Create einfügt, damit Exceptions darin kein Speicherleck verursachen. Bei normalen Funktionen muss man das halt selbst machen.

Gleichzeitig muss man aufpassen das kein Parametrisiertes Exit vorkommt, bzw wenn, das dann Result gefreed wird:

Code: Alles auswählen

 Result := TStringList.Create;
  ...
  Result.Free; // muss vor parametrisiertem exit immer aufgerufen werden
  Exit(nil);
Das führt einfach nur mehr Komplexität ein, und ist was was man sehr einfach vergessen oder Falsch machen kann.
Klar ist das Funktionsergebnis eigentlich immer schöner als einen Parameter zu übergeben, aber unter diesen Gesichtspunkten sollte man es, um die Komplexität gering zu halten und möglichst Robusten Codes zu schreiben, doch besser als Parameter zurückgeben

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

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von kupferstecher »

Winni hat geschrieben:
Do 27. Jan 2022, 22:46
Du musst doch erstmal den Button erzeugen!
Du hast mich falsch verstanden, der Versuch war Button1 über Result zu übergeben, Button1 ist außerhalb schon da. D.h. mir ging es darum, ob Result tatsächlich als Parameter (!) übergeben wird, wie sich das laut Doku anhört. Mit Strings funktioniert es offenbar, siehe untenstehenden Code. Allerdings hat es nur bei Zuweisung zu einer lokalen Variablen vText funktioniert, nicht wenn sie global definiert ist.

Code: Alles auswählen

procedure TForm1.FormCreate(Sender:TObject);
var vText: string;
begin
  vText:= 'Aber';
  vText:= AddHallo;
  Label1.Caption:= vText; //-> Ausgabe 'AberHallo'

end;

Function TForm1.AddHallo:string;
begin
  Result:= Result + 'Hallo';
end;

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: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von PascalDragon »

kupferstecher hat geschrieben:
Do 27. Jan 2022, 22:19
Allerdings heißt es in der Doku:
Remark: Function results are treated as pass-by-reference parameters. That is especially important for managed types: The function result may be non-nil on entry, and set to a valid instance of the type.
Ein kurzer Test hat zum Crash geführt (Segmentation Vault):

Code: Alles auswählen

procedure TForm1.Button1Click(Sender:TObject);
begin
  Button1:= SetLblCaption;
end;

function TForm1.SetLblCaption:TButton;
begin
  if Result is TButton //<- SigSegV
  then Label1.Caption:= Result.Name;
end;
Vielleicht kann das ein PascalDragon kurz kommentieren :-)
Da hat das jemand etwas inkorrekt hinzugefügt: das mit dem pass-by-reference gilt überhaupt nur für managed types sowie große strukturierte Typen (record, object) oder Felder (array), wobei „groß” sich hier auf „größer Registerbreite” bezieht.
FPC Compiler Entwickler

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

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von kupferstecher »

Danke!

Habs nochmal mit einem Record versucht, da ist das gleiche: Wird das Ergebnis einer lokalen Variable zugewiesen, funktioniert es. Ist es aber eine globale Variable kommt ein falscher Wert raus. Aber ist letztlich auch nicht wichtig.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Winni »

kupferstecher hat geschrieben:
Fr 28. Jan 2022, 22:01
Danke!

Habs nochmal mit einem Record versucht, da ist das gleiche: Wird das Ergebnis einer lokalen Variable zugewiesen, funktioniert es. Ist es aber eine globale Variable kommt ein falscher Wert raus. Aber ist letztlich auch nicht wichtig.
Natürlich ist das wichtig. Aber ich verstehe nicht, was Du uns sagen willst.

Ganz einfach, probier das:

Code: Alles auswählen



Type TRec = record
            vorname,
            nachname : string;
            end;

var globalRec : Trec;

function FillRec : Trec;
begin
 Result.vorname:= 'Karl';
 result.nachname:='Napp';
end;

procedure TForm1.Button3Click(Sender: TObject);
var   localRec : TRec;

   procedure ShowRec (R: TRec);
   begin
   showMessage (R.vorname+#32+R.nachname);
   end;

begin
globalRec := FillRec;
ShowRec(globalRec);
localRec := FillRec;
ShowRec(localRec);
end;                      

Winni

Benutzeravatar
six1
Beiträge: 788
Registriert: Do 1. Jul 2010, 19:01

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von six1 »

und jetzt mit einer TStringlist im TRect....
Über Strings haben wir ja bereits mehrfach erfahren, dass diese nicht ge-free-t werden müssen
Gruß, Michael

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Winni »

Hi!

Na gut, mit StringList:

Code: Alles auswählen

Type TRec = record
            sl: TStringList;
            end;

var globalRec : Trec;

function FillRec : Trec;
begin
 Result.sl := TStringList.create;
 result.sl.add ('Karl');
 result.sl.add ('Napp');
end;

procedure TForm1.Button3Click(Sender: TObject);
var   localRec : TRec;

   procedure ShowRec (R: TRec);
   var s : string='';
					i: Integer;
   begin
   for i := 0 to R.sl.count -1 do s := s +R.sl[i]+#32;
   showMessage (s);
			end;

begin
globalRec := FillRec;
ShowRec(globalRec);
localRec := FillRec;
ShowRec(localRec);
globalRec.sl.free;
localRec.sl.free;
end;

Winni

Benutzeravatar
six1
Beiträge: 788
Registriert: Do 1. Jul 2010, 19:01

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von six1 »

Naja, du umschiffst das Problem durch Verwendung einer globalen Variable.
Als Function mit Rückgabewert TStringlist bleibt es ein Problem.
Nachdem die Function verlassen wird, kann man auf das erzeugte Objekt nicht mehr zugreifen, um es zu zerstören.
Gruß, Michael

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von Winni »

Hi!

Also: Ich greif mit einer lokalen und einer lokalen Variablen auf die Funktion zu.

Dass dass man nach dem Verlassen der Funktion nicht mehr von der Funktion auf das Funktions-Resultat zugreifen kann, ist Grundlage der Syntax von Pascal und trivial.

Darab muss man sich gewöhnen. Oder sich ne schlechtere Sprache suchen.

Winni

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

Re: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von kupferstecher »

Winni hat geschrieben:
Sa 29. Jan 2022, 16:53
Natürlich ist das wichtig. Aber ich verstehe nicht, was Du uns sagen willst.
Ausgangspunkt war, dass der TE in einem späteren Post - wohl versehentlich - das Objekt über die Zuweisung quasi rückwärts in die Funktion als Result geben wollte. Natürlich würde man sowas nicht machen, aber ich fand es interessant zu wissen, ob das tatsächlich geht.
Ganz einfach, probier das:
Dein Beispiel verwendet nur den Typ, nicht aber den Wert aus der Result-Variablen. Die Doku spricht von "treated as pass-by-reference parameters". Ein Parameter nach meinem Dafürhalten ist eine Variable mit Wert, die an die Funktion übergeben wird. Der Wert stimmt aber bei meinen Tests bei globalen Variabeln nicht. Also funktioniert nicht. Ist aber wie gesagt nicht wichtig, weil man so sowieso nicht programmieren würde. Wäre eher schlimm, wenn das verlässlich funktionieren würde.

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: TSringList als Funktions-Rückgabe sinnvoll?

Beitrag von PascalDragon »

kupferstecher hat geschrieben:
Fr 28. Jan 2022, 22:01
Habs nochmal mit einem Record versucht, da ist das gleiche: Wird das Ergebnis einer lokalen Variable zugewiesen, funktioniert es. Ist es aber eine globale Variable kommt ein falscher Wert raus. Aber ist letztlich auch nicht wichtig.
Der Compiler verwendet in dem Fall meist eine temporale Variable, die auf dem Stack liegt. Versuche es mal zweimal hintereinander ;)

Code: Alles auswählen

program ttest;

{$mode objfpc}{$H+}

type
  TTestRec = record
    f1, f2: SizeInt;
  end;

function Test: TTestRec;
begin
  Writeln(Result.f1, ' ', Result.f2);
  Result.f1 := 42;
  Result.f2 := 21;
end;

var
  f: TTestRec;
begin
  f := Test; // writes garbage
  f := Test;  // writes "42 21"
end.
FPC Compiler Entwickler

Antworten