Strings in TList

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Strings in TList

Beitrag von photor »

Hallo Forum,

erstmal ein Code-Schnipsel aus einem Programm, dass ich übernommen habe, und ich brauche ein bisschen Unterstützung für das Verständnis:

Code: Alles auswählen

 
var
  begList : TList;
  PString : PStr;    // pointer auf String
begin
  begList.Create; 
  begList.clear;
 
  aString := '';
  // aString zusammen bauen
  For i:=0 to nCount-1 do
    aString := aString+ '3' + #32;
 
  // String in Liste
  New(Pstring);
  PString^ := aString;
  begList.Add(PString);
 
  // tue was neues mit dem String
  aString := '';
  aString := aString + 'ingendwas';
 
  // Liste wieder freigeben
  For i:=begList.Count-1 downto 0 do
  begin
    PString := begList.Items[i];
    Dispose(PString);
    begList.Delete(i);
    begList.Free;
  end;
end;
 

es wird eine TList, in die dann Strings eingehängt werden sollen (vielleicht sollte man hier besser ein TStringList nehmen), creatiert. Der anzuhängende String in der Routine erst zusammengebaut, was mittels aString geschieht. Um den an die Liste zu hängen, wird ein Pointer auf String-Speicherplatz mit New() besorgt, dem der zusammengebauten String zugewiesen wird. Dieser Pointer zeigt nun auf Speicher, der Inhalt von aString enthält; der Pointer wird an die TList gehängt, richtig?

Das erscheint mir schon eher kompliziert. Geht das einfacher? Muss ich tatsächlich den aString umspeichern, um zu vermeiden, dass anschließend der String in der TList geändert wird; der String aString wird schließlich im folgenden weiter verwendet, mit neuem Inhalt beschrieben, und wieder an die TList angehängt? Die bereits angehängte Information soll nicht verloren gehen.

Zum Schluss wird der Speicher wieder freigegeben, was auch eher kompliziert geschieht: erst werden alle Einträge einzeln gelöscht, entfernt und der entsprechende Speicher freigegeben. Zum Schluss wird die TList freigegeben. Reicht es nicht, nur TList.Free aufzurufen oder bleiben dann Speicherleichen zurück (Memory Leak würde ich natürlich vermeiden wollen - das ganze Konstrukt wird gute 20 mal angewendet :roll: )?

Wäre schön, wenn Ihr mich erleuchten könntet. Ich versuche, den Code zu entwirren und zu vereinfachen, wo es geht.

Danke,
Photor

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Strings in TList

Beitrag von Mathias »

TList verwendet man in der Regel nicht nackt, sondern man vererbt dies.

Code: Alles auswählen

type
  TMyStringList = class(TList)
    ...
  end;
 


Ich habe mal eine kleine Komponete geschrieben, welche eine Art TStringList baut, wobei zusätzlich zu jeder Zeile eine Farbe angegeben werden kann.
Es ist auch eine Art Memo vorhanden, welche die Zeilen farbig darstellt.

Vielleicht hilft dir dies weiter.

Code: Alles auswählen

unit MyColorString;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Forms, Controls, Graphics, StdCtrls, ExtCtrls;
 
type
  PColorString = ^TColorString;
 
  TColorString = record
    s: string;
    col: TColor;
  end;
 
  { TColorStringList }
 
  TColorStringList = class(TList)
  private
    FOnChange: TNotifyEvent;
    function GetItem(index: integer): PColorString;
    procedure SetItem(index: integer; AValue: PColorString);
  public
    property Items[index: integer]: PColorString read GetItem write SetItem; default;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
 
    destructor Destroy; override;
 
    function Add(s: string; col: TColor): integer;
  end;
 
  { TColorMemo }
 
  TColorMemo = class(TCustomPanel)
  private
    FLines: TColorStringList;
    VertScrollBar, HoriScrollBar: TScrollBar;
    procedure LinesChanged(Sender: TObject);
    procedure SchrollBarChanged(Sender: TObject);
    procedure ColorMemoResize(Sender: TObject);
  protected
    procedure Paint; override;
  public
    property Lines: TColorStringList read FLines write FLines;
 
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
  end;
 
implementation
 
{ TColorMemo }
 
constructor TColorMemo.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  Color := clBlack;
 
  Lines := TColorStringList.Create;
  Lines.OnChange := @LinesChanged;
 
  OnResize := @ColorMemoResize;
 
  VertScrollBar := TScrollBar.Create(Self);
  HoriScrollBar := TScrollBar.Create(Self);
 
  with VertScrollBar do begin
    Align := alRight;
    Kind := sbVertical;
    //    BorderSpacing.Bottom:=0;
    Visible := False;
    LargeChange := 100;
    OnChange := @SchrollBarChanged;
    Parent := Self;
  end;
 
  with HoriScrollBar do begin
    Align := alBottom;
    Kind := sbHorizontal;
    BorderSpacing.Right := VertScrollBar.Width;
    //    Visible := False;
    Parent := Self;
  end;
end;
 
destructor TColorMemo.Destroy;
begin
  Lines.Free;
  inherited Destroy;
end;
 
procedure TColorMemo.LinesChanged(Sender: TObject);
begin
  ColorMemoResize(Sender);
end;
 
procedure TColorMemo.SchrollBarChanged(Sender: TObject);
begin
  ColorMemoResize(Sender);
end;
 
procedure TColorMemo.Paint;
var
  h, i, a, e: integer;
begin
  inherited Paint;
 
  h := Canvas.TextHeight('@');
 
  a := VertScrollBar.Position div h;
  e := a + Height div h - 1;
  if e > Lines.Count - 1 then begin
    e := Lines.Count - 1;
  end;
 
  for i := a to e do begin
    Canvas.Font.Color := Lines[i]^.col;
    Canvas.TextOut(0, h * i - VertScrollBar.Position, Lines[i]^.s);
  end;
end;
 
procedure TColorMemo.ColorMemoResize(Sender: TObject);
var
  h, anzZeilen: integer;
begin
  h := Canvas.TextHeight('@');
  anzZeilen := Height div h;
  with VertScrollBar do begin
    Visible := anzZeilen < Lines.Count;
    //    WriteLn('ps: ', PageSize, '  max: ', max, '  pos: ', Position);
    PageSize := Height;
    Max := h * Lines.Count;
    if Position > Max - PageSize then begin
      Position := Max - PageSize;
    end;
  end;
 
  Invalidate;
end;
 
{ TColorStringList }
 
function TColorStringList.GetItem(index: integer): PColorString;
begin
  Result := PColorString(inherited Get(index));
end;
 
procedure TColorStringList.SetItem(index: integer; AValue: PColorString);
begin
  inherited Items[index] := AValue;
end;
 
destructor TColorStringList.Destroy;
var
  cs: PColorString;
  i: integer;
begin
  for i := 0 to Self.Count - 1 do begin
    cs := inherited Items[i];
    Dispose(cs);
  end;
 
  inherited Destroy;
end;
 
function TColorStringList.Add(s: string; col: TColor): integer;
var
  cs: PColorString;
begin
  New(cs);
  cs^.s := s;
  cs^.col := col;
  Result := inherited Add(cs);
  if Assigned(FOnChange) then begin
    FOnChange(self);
  end;
end;
 
end.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Strings in TList

Beitrag von wp_xyz »

photor hat geschrieben:vielleicht sollte man hier besser ein TStringList nehmen

Nicht "vielleicht", sondern "unbedingt", TStringList wurde genau dafür gemacht. Außerdem sind Strings sowieso schon Pointer. Wenn du durch Stringverkettung den String s := 'irgend' + 'was' zusammenbaust, ist s ein Pointer auf den Ergebnisstring, und durch den eingebauten Referenzzähler wird erkannt, wann der String nicht mehr gebraucht wird, so dass dann der belegte Speicher automatisch freigegeben wird. Da brauchst du mit Strings kein New/Dispose aufzurufen.

photor hat geschrieben:Reicht es nicht, nur TList.Free aufzurufen.

Nein. TList speichert ja nur Zeiger und weiß nicht, wie der davon belegte Speicher freizugeben ist. Daher räumt TList nur den selbst belegten Speicher auf, nicht aber den Speicher, auf den die Zeiger zeigen. Wären die Zeiger dagegen Objekte, dann hätre jedes ja einen virtuellen Destructor, und TList könnte für jedes Objekt sein .Free aufrufen. Allerdings ist das nicht in TList implementiert (denn ein Pointer ist nicht notwenigerweise ein TObject), sondern in TObjList.

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Strings in TList

Beitrag von photor »

wp_xyz hat geschrieben:
photor hat geschrieben:vielleicht sollte man hier besser ein TStringList nehmen

Nicht "vielleicht", sondern "unbedingt", TStringList wurde genau dafür gemacht. Außerdem sind Strings sowieso schon Pointer. Wenn du durch Stringverkettung den String s := 'irgend' + 'was' zusammenbaust, ist s ein Pointer auf den Ergebnisstring, und durch den eingebauten Referenzzähler wird erkannt, wann der String nicht mehr gebraucht wird, so dass dann der belegte Speicher automatisch freigegeben wird. Da brauchst du mit Strings kein New/Dispose aufzurufen.

Das mit der TStringList war mir im Prinzip klar. Ich wollte nur sicher gehen, dass ich nicht irgendeinen Aspekt vergessen habe, der die TList rechtfertigt.
Zum String: es reicht also, den String aString direkt an die TList/TStingList anzuhängen. Der bleibt also auch so da, selbst wenn ich aString anschließend lösche und neu belege, um den nächsten Eintrag für die Liste zu bauen.

wp_xyz hat geschrieben:
photor hat geschrieben:Reicht es nicht, nur TList.Free aufzurufen.

Nein. TList speichert ja nur Zeiger und weiß nicht, wie der davon belegte Speicher freizugeben ist. Daher räumt TList nur den selbst belegten Speicher auf, nicht aber den Speicher, auf den die Zeiger zeigen. Wären die Zeiger dagegen Objekte, dann hätre jedes ja einen virtuellen Destructor, und TList könnte für jedes Objekt sein .Free aufrufen. Allerdings ist das nicht in TList implementiert (denn ein Pointer ist nicht notwenigerweise ein TObject), sondern in TObjList.

Ok. Dann muss das korrekterweise so gemacht werden. Gut. Werde ich beherzigen.

Danke,
Photor

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Strings in TList

Beitrag von wp_xyz »

photor hat geschrieben:
wp_xyz hat geschrieben:
photor hat geschrieben:vielleicht sollte man hier besser ein TStringList nehmen

Das mit der TStringList war mir im Prinzip klar. Ich wollte nur sicher gehen, dass ich nicht irgendeinen Aspekt vergessen habe, der die TList rechtfertigt.
Zum String: es reicht also, den String aString direkt an die TList/TStingList anzuhängen. Der bleibt also auch so da, selbst wenn ich aString anschließend lösche und neu belege, um den nächsten Eintrag für die Liste zu bauen.


Beweis:

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
 
uses
  Classes;
 
var
  SL: TStringList;
  L: TList;
  s: String;
begin
 
  SL := TStringList.Create;
  try
    s := 'Hallo Welt.';
    SL.Add(s);
    s := '';
    WriteLn('Aus StringList: "', SL[0], '"');
  finally
    SL.Free;
  end;
 
  // TStringList ist viel einfacher, aber es geht auch mit TList
  L := TList.Create;
  try
    s := 'Hallo Welt.';
    L.Add(pointer(s))// s ist zwar ein Pointer, aber hier muss ein Type-Cast gemacht werden, sonst macht der Compiler nicht mit
    s := '';
    WriteLn('Aus List: "', string(L[0]), '"');   // Pointer zum String zurückverwandeln!
  finally
    L.Free;
  end;
 
  ReadLn;
 
end.

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

Re: Strings in TList

Beitrag von Warf »

wp_xyz hat geschrieben:Beweis:

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
 
uses
  Classes;
 
var
  SL: TStringList;
  L: TList;
  s: String;
begin
 
  SL := TStringList.Create;
  try
    s := 'Hallo Welt.';
    SL.Add(s);
    s := '';
    WriteLn('Aus StringList: "', SL[0], '"');
  finally
    SL.Free;
  end;
 
  // TStringList ist viel einfacher, aber es geht auch mit TList
  L := TList.Create;
  try
    s := 'Hallo Welt.';
    L.Add(pointer(s))// s ist zwar ein Pointer, aber hier muss ein Type-Cast gemacht werden, sonst macht der Compiler nicht mit
    s := '';
    WriteLn('Aus List: "', string(L[0]), '"');   // Pointer zum String zurückverwandeln!
  finally
    L.Free;
  end;
 
  ReadLn;
 
end.


So ne ganz dumme frage, wenn du den string zu nem Pointer castest und dann zu L hinzufügst, bleibt dann die referenzzählung erhalten (wird refcount dann erhöht)? Denn wie ich mir das jetzt denken würde ist das bei s := '' der alte speicher freigegeben wird (wenn die refcount nicht erhöt wird), und der grund das es trozdem funktioniert ist die tatsache das die vorrige memorylocation noch nicht überschrieben wurde. Denn dieser Code erzeugt auch kein memory leak (was ja geschehen sollte da die TList den string nicht freigeben kann)

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Strings in TList

Beitrag von wp_xyz »

Sicher bin ich mir da auch nicht - schon aus diesem Grund würde ich nie auf die Idee kommen, TList zum Speichern von Strings zu verwenden. Aber ich habe vor dem Posten mit HeapTrc auf Speicherlecks geprüft und nichts gefunden, auch nachdem ich die Zuweisung s := '' durch s := 'abc' ersetzt habe.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Strings in TList

Beitrag von Mathias »

auch nachdem ich die Zuweisung s := '' durch s := 'abc' ersetzt habe.
Könnte es nicht sein, das bei den 3 Zeichen der Pointer nicht geändert wurde, wie sieht es aus, wen du den String mit SetLength (s, 64000); änderst ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Strings in TList

Beitrag von wp_xyz »

Auch ok.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Strings in TList

Beitrag von Mathias »

Dann speichert TList den Pointer des String und nicht den Inhalt des Strings.

Folgender Code bringt bei Writeln zwei total unterschiedliche Werte.

Code: Alles auswählen

var
  s:String;
begin
  s:='12345';
  WriteLn(PtrInt(@s));
  WriteLn(PtrInt(@s[1]));
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Strings in TList

Beitrag von Warf »

wp_xyz hat geschrieben:Sicher bin ich mir da auch nicht - schon aus diesem Grund würde ich nie auf die Idee kommen, TList zum Speichern von Strings zu verwenden. Aber ich habe vor dem Posten mit HeapTrc auf Speicherlecks geprüft und nichts gefunden, auch nachdem ich die Zuweisung s := '' durch s := 'abc' ersetzt habe.


Es geht mir mehr darum das WriteLn('Aus List: "', string(L[0]), '"'); ein illegalen speicherzugriff macht weil der string der an addresse L[0] liegt bei s := '' oder s := 'abc' gefreed werden müsste (da fpc ja denken müsste es gibt keine referenz mehr da der Pointer cast diese ja nicht erhöhen sollte). Das problem ist, sowas ist schwer zu prüfen, denn selbst wenn der speicher gefreed wurde, aber noch nicht überschrieben wurde, kann es sein das an der addresse immer noch der selbe string liegt.

Bei der referenzzählung wäre ich generell sehr vorsichtig.

Ansonsten wenn man keine Lust auf eine StringList hat kann man eventuell auch eine TFPGList verwenden

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Strings in TList

Beitrag von wp_xyz »

Hast Recht. Auch wenn ich nichts Negatives gesehen habe, kann da einiges an Problemen begraben liegen. Diese Konstruktion sollte man wirklich nicht anwenden, zumal es mit TStringList eine ausgezeichnete Alternative gibt.

Antworten