External SIGSEGV in TList beim Zählen der Listeneinträge

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
thosch
Beiträge: 324
Registriert: Mo 10. Jul 2017, 20:32

External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von thosch »

Hallo,

ich habe einen seltsamen Fehler in FPC 3.0.2 mit einer TList Klasse.

Code: Alles auswählen

 
type
  tMyRec = record
    Zahl: Integer;
    Name: String;
  end;
 
var
  ARec: TMyRec;
  mylist: TMyList;  // -> class(TList);
 
myList.Add(@ARec);   // einen Record einfügen in ein TList Objekt;
 
function TmyList.Find(Item: String; var wc: TMyRec): Boolean;
var Index: Integer;
begin
  Result := false; Index := 0;
  while Index < self.Count-1 do
  begin
    if TMyList(Items[Index]).fRec.Name = Item then
    begin
      wc := fRec;
      Result := true;
      Index := self.Count;  while Schleife beenden!
    end;
    inc(Index);  hier die External SIGSEGV !!! Warum ???
  end;
end;
 


Warum bekomme ich beim Hochzählen hier die Exception?

Ich will, wenn der Listeneintrag noch nicht gefunden ist, den nächsten Eintrag abfragen. Deshalb das Increment. Wenn Eintrag gefunden, setze ich den Index auf Listenende. Der wird dann noch mal erhöht und dürfte doch dann einfach nur die Schleifeneintrittsbedingung nicht mehr erfüllen. Dann sollte doch gar nicht mehr in die Schleife gesprungen werden. Wenn Eintrag gefunden -> Index = Count. Dieser index wird dann noch auf Count + 1 gesetzt -> Schleifeneintrittsbedingung Index < Count nicht mehr erfüllt. Warum da die Exception?

Hat das was smit Windows Multitasking zu tun. Ich habe keine Threads verwendet. Kann es sein, dass Windows da dennoch durcheinander kommt, weil TList nicht threadsicher ist?

.

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von wp_xyz »

Generell sollte das Vorgehen so funktionieren wie von dir beabsichtigt, auch wenn ich es anders machen würde, nämlich mit "break" oder "exit" gleich aus der Schleife bzw. Funktion springen.

Falsch, aber nicht für das Fehlverhalten verantwortlich, ist wahrscheinlich die Zeile "while Index < self.Count-1" - das ignoriert den letzten Listeneintrag, es sei denn das ist so beabsichtigt.

Was ist das isolierte "fRec" zwei Zeilen drunter? Fehlt hier ein "TMyList(Items[Index])" vorneweg?
Ob ARec eine globale oder lokale Variable ist, weiß man auch nicht. Bei einer lokalen Variablen hinterlässt "MyList.Add(@ARec)" in der Liste nur Müll, wenn die Einfügeroutine verlassen wird.
Auch dass du die List-Items nach TMyList castest ist seltsam...

Ein compilierbares Demoprogramm wäre sinnvoller gewesen als halbfertiger Pseudo-Code.

thosch
Beiträge: 324
Registriert: Mo 10. Jul 2017, 20:32

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von thosch »

Ok hier das Programm!

Code: Alles auswählen

 
program listdemo;
 
uses Classes, Sysutils;
 
type
  TMyRec = record
     Zahl: Integer;
     Name: String;
  end;
  TMyList = class(TList)
  private
     fRec: TMyRec;
  public
    function find(Item: String;
  end;
 
function TmyList.Find(Item: String; var wc: TMyRec): Boolean;
var Index: Integer;
begin
  Result := false; Index := 0;
  while Index < self.Count-1 do
  begin
    if TMyList(Items[Index]).fRec.Name = Item then
    begin
      wc := fRec;
      Result := true;
      Index := self.Count;  while Schleife beenden!
    end;
    inc(Index);  hier die External SIGSEGV !!! Warum ???
  end;
end;
 
var
  ARec: TMyRec;
  AList: TMyList;
 
begin
  ARec.Zahl := 1;
   ARec.Name := 'Anderas';
   AList := TMyList.Create;
   AList.Add(@ARec);
  ARec.Zahl := 2;
   ARec.Name := 'Bettina';
   AList.Add(@ARec);
   ARec.Zahl := 3;
   ARec.Name := 'Claus';
   AList.Add(@ARec);
 
   if AList.Find('Bettina', ARec) then { mach irgendwas damit };
end.
 


Wenn ich eine For Schleife wähle, kommt die Exception in der For Index:=0 to self.Count-1 do Zeile. So habe ich hier die While Schleife benutzt, da ich hier Index bewusst selber auf 0 setzen muss.

Habe keine Erkärung für dises Verhalten (Freepascal 3.0.2)
.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von mse »

Das kann nicht gutgehen:

Code: Alles auswählen

 
[...]
    if TMyList(Items[Index]).fRec.Name = Item then
        //hier interpretierst du den listeneintrag als TMylist
[...]
   AList.Add(@ARec);
       //hier speicherst du ^TMyRec
 

thosch
Beiträge: 324
Registriert: Mo 10. Jul 2017, 20:32

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von thosch »

Und warum ist das falsch, habe ich dochb so gemacht. Das Feld Items aus TList ist doch vom Typ Pointer. Woher soll TList da wissen, was in diesem Pointer gespeichert ist, wenn diese Typumwandlung in TMyList falsch ist.

Welche Workarounds gibt es da?

Oder kann es sein, dass es da einen Konflikt gibt, weil ich den Record abfrage und nicht den Zeiger auf den Record?
Zuletzt geändert von thosch am Fr 16. Nov 2018, 09:30, insgesamt 1-mal geändert.

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von fliegermichl »

Ja eben. Die Items in der Liste von TMyList sind vom Typ TMyRec und nicht TMyList.

Der Fehler ist in der Zeile

Code: Alles auswählen

 
 if TMyList(Items[Index]).fRec.Name = Item then
 


Hier sagst du, das Item vom Typ TMyList sei. Das ist aber falsch er ist vom Typ TMyRec also muss es lauten

Code: Alles auswählen

 
 if TMyRec(Items[Index]).fRec.Name = Item then
 

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von mse »

Die TList weiss es eben nicht, das liegt in deiner Verantwortung. Wenn du angibst, dass der pointer eine TMylist ist, musst du auch dafür sorgen, dass in der Liste TMylist gespeichert sind und nicht ^TMyrec. Ich bin mir nicht sicher, ob du tatsächlich TMylist als TMylist-Elemente haben willst.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von mse »

fliegermichl hat geschrieben:Hier sagst du, das Item vom Typ TMyList sei. Das ist aber falsch er ist vom Typ TMyRec also muss es lauten

Code: Alles auswählen

 
 if TMyRec(Items[Index]).fRec.Name = Item then
 

Was wahrscheinlich ebenfalls schief läuft, da die gespeicherten pointer möglicherweise auf dem Stack liegen und ihre Gültigkeit verlieren können.
Vermutlich gewollt ist eine Klasse welche TMyRec enthält. Bitte das Abräumen der erstellten Items nicht vergessen. TObjectList wäre auch noch ein Stichwort.
https://www.freepascal.org/docs-html/fc ... tlist.html

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von wp_xyz »

Die Items der Liste sind pointer. Die Add-Anweisung zeigt, dass zu Zeiger auf TMyRec speicherst. Deklariere einen Typ für diese Zeiger:

Code: Alles auswählen

type
  TMyRec = record
     Zahl: Integer;
     Name: String;
  end;
  PMyRec = ^TMyRec;

In der Find-Routine versuchst du den Item zu finden, bei dem das Feld Name dem übergebenen Parameter entspricht. Daher musst du die Listen-Elemente nach PMyRec casten, denn diese sind in der Liste gespeichert. Dein Cast nach TMyList geht auf jeden Fall schief!

Zweiter Fehler: Du hast in der Listen-Definition ein ominöses Feld fRec. Wozu? Ich denke eher, du weißt nicht, wie man auf die Listenelemente zugreift und versuchst dich daran irgendwie vorbeizumogeln. Richtig ist das:

Code: Alles auswählen

 
function TmyList.Find(Item: String; var wc: TMyRec): Boolean;
var Index: Integer;
begin
  Result := false; Index := 0;
  while Index < self.Count do   // self.Count-1 ist falsch, weil der letzte Listeneintrag nicht geprüft wird!
  begin
//    if TMyList(Items[Index]).fRec.Name = Item then
    if PMyRec(Items[Index])^.Name = Item then
    begin
      wc := PMyRec(Items[Index])^;
//      wc := fRec;
      Result := true;
      Index := self.Count;
    end;
    inc(Index);
  end;
end;

Damit sollte dein Programm nicht mehr abstürzen. Es läuft aber immer noch nicht richtig, weil das Element "Bettina" nicht gefunden wird, obwohl du es ja hinzugefügt hast.

Du machst dir beim Befüllen der Liste das Leben zu einfach. Ein globaler Record, dessen Elementen du immer wieder andere Werte zuweist, ist immer noch dieselbe Variable, d.h. dessen Adresse @ARec ist immer dieselbe.

Richtig wäre, für jedes Listenelement eine eigene Variable anzulegen:

Code: Alles auswählen

var
  ARec1, ARec2, ARec3: TMyRec;
begin
   AList := TMyList.Create;
   ARec1.Zahl := 1;
   ARec1.Name := 'Anderas';
   AList.Add(@ARec1);
   ARec2.Zahl := 2;
   ARec2.Name := 'Bettina';
   AList.Add(@ARec2);
   ARec3.Zahl := 3;
   ARec3.Name := 'Claus';
   AList.Add(@ARec3);

Oder noch besser: Speichere die Variablen auf dem Heap:

Code: Alles auswählen

var
  ARec: PMyRec;
begin
  AList := TMyList.Create;
 
  New(ARec);
  ARec^.Zahl := 1;
  ARec^.Name := 'Andreas';
  AList.Add(ARec);
 
  New(ARec);
  ARec^.Zahl := 2;
  ARec^.Name := 'Bettina';
  AList.Add(ARec);
 
  New(ARec);
  ARec^.Zahl := 3;
  ARec^.Name := 'Claus';
  AList.Add(ARec);

Was jetzt noch fehlt, ist am Programm-Ende die Listenelemente wieder vom Heap zu löschen. und auch die Liste zu löschen.

Alles in allem funktioniert es so:

Code: Alles auswählen

program listdemo;
 
uses Classes, Sysutils;
 
type
  TMyRec = record
     Zahl: Integer;
     Name: String;
  end;
  PMyRec = ^TMyRec;
 
  TMyList = class(TList)
  private
     fRec: TMyRec;
  public
    function Find(Item: String; var wc: TMyRec): Boolean;
  end;
 
function TmyList.Find(Item: String; var wc: TMyRec): Boolean;
var Index: Integer;
begin
  Result := false; Index := 0;
  while Index < self.Count do
  begin
//    if TMyList(Items[Index]).fRec.Name = Item then
    if PMyRec(Items[Index])^.Name = Item then
    begin
      wc := PMyRec(Items[Index])^;
//      wc := fRec;
      Result := true;
      Index := self.Count//while Schleife beenden!
    end;
    inc(Index);
  end;
end;
 
var
  ARec: PMyRec;
  AList: TMyList;
  foundRec: TMyRec;
  i: Integer;
 
begin
  AList := TMyList.Create;
 
  try
    New(ARec);
    ARec^.Zahl := 1;
    ARec^.Name := 'Anderas';
    AList.Add(ARec);
 
    New(ARec);
    ARec^.Zahl := 2;
    ARec^.Name := 'Bettina';
    AList.Add(ARec);
 
    New(ARec);
    ARec^.Zahl := 3;
    ARec^.Name := 'Claus';
    AList.Add(ARec);
 
    WriteLn(AList.Count, ' items');
    for i:=0 to AList.Count-1 do
      with PMyRec(AList[i])^ do
        WriteLn('Index ', i, ': Zahl ', Zahl, ', Name: ', Name);
 
    if AList.Find('Claus', FoundRec) then WriteLn('found') else WriteLn('not found');
 
  finally
    for i := 0 to AList.Count-1 do
      Dispose(PMyRec(AList[i]));
    AList.Free;
  end;
 
  ReadLn;
 
end.

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von theo »

Was wp_xyz schreibt ist gut und richtig.
Trotzdem würde ich, wenn du schon eine Ableitung von TList hast, dafür natürlich eine Methode in TMyList machen.
z.B AList.AddItem(Name, Zahl); statt zig mal New(ARec) etc. zu schreiben.
Auch das disposen kann die TMyList bei ihrem eigenen "Abgang" übernehmen.

thosch
Beiträge: 324
Registriert: Mo 10. Jul 2017, 20:32

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von thosch »

Danke wie verrückt Euch allen. So wie von @wp_xyz beschrieben, funktioniert die Suche und vorher das Füllen der Liste.

Dieser habe ich nun einen Destructor spendiert, der nun die Records nach wp_xyz's Methode nach Gebrauch frei gibt.

Ich benutze nun auch einen Zeiger auf den Record als Variable, da ja an die Liste ein Pointer übergeben werden muss.

Mein Fehler war diese Konstruktion:

if TMyList(Items[Index]).fRec.Name = Item then

fRec ist ein Feld der Liste TMyLIst, von TList abgeleitet. Aber ich hatte keinen Zeiger auf den Record (PMyRec =^TMyRec) definiert und wollte nun mit TMyRec auf das Listenelement zugreifen.

Mein zweiter Fehler war, siehe hier die if TMylist ... Zeile, dass ich Items[Index] nicht mit dem Recordtyp, sondern mit dem Listentyp TMyList gecastet hatte. Weil ich so auf fRec zugreifen wollte, einem Feld meiner TMylist. Das musste schief gehen.

Da ja Items ein Zeiger auf die interne Liste mit meinen Records ist, die als Pointer gespeichert sind, muss ich mit PMyRec(Items[IIndex])^ casten, nicht mit TMyList. Mein Record enthält ja gar kein Feld mit der Bezeichnung fRec. Und fRec der Liste kann eh nur eines der Listenelmente maximal enthalten. Ist aber durch Add (Methode der Liste) gar nicht belegt. Ich übergebe ja dort den Record als Parameter der Add Methode, brauche daher fRec überhaupt nicht. Hatte dieses Feld aber quick and dirty beim Abtippen des Demo Programmes mit eingebracht.

Manchmal hat man halt ein ganz dickes Brett vorm Kopf. :)

Nun aber funktioniert das Suchen in der liste und auch das Befüllen derselben.

Danke Euch allen! :D

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von fliegermichl »

Noch einfacher wäre natürlich, statt des Records eine Klasse zu verwenden.

Code: Alles auswählen

 
type
 TMyRec = class
 private
  fName : string;
  fZahl : integer;
 public
  constructor Create(aName : string; aZahl : integer);
  property Name : string read fName write fName;
  property Zahl : integer read fZahl write fZahl;
 end;
 
 TMyList = class ( TList )
  function Get(Index : integer) : TMyRec;
  procedure Put(Index : integer; Node : TMyRec);
  destructor Destroy; override;
  property Items[Index : integer] : TMyRec read Get write Put; default;
 end;
 
implementation
 
// TMyRec
constructor TMyRec.Create(aName : string; aZahl : integer);
begin
 inherited Create;
 fName := aName;
 fZahl := aZahl;
end;
 
// TMyList
function TMyList.Get(Index : integer) : TMyRec;
begin
 Result := TMyRec(inherited Get(Index));
end;
 
procedure TMyList.Put(Index : integer; Item : TMyRec);
begin
 inherited Put(Index, Item);
end;
 
destructor TMyList.Destroy;
var i : integer;
begin
 for i := 0 to Count - 1 do Items[i].Free;
 clear;
 inherited;
end;
 


Dann kannst du einfach Einträge hinzufügen mit

Code: Alles auswählen

 
 with MyList do
 begin
   Add(TMyRec.Create('Andreas', 1));
   Add(TMyRec.Create('Bettina', 2));
   Add(TMyRec.Create('Claus', 3));
 end;
 


und wenn dein Programm fertig ist, rufst du einfach MyList.Free auf und es werden auch alle Listeneinträge ordentlich entsorgt.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von mse »

fliegermichl hat geschrieben:und wenn dein Programm fertig ist, rufst du einfach MyList.Free auf und es werden auch alle Listeneinträge ordentlich entsorgt.

Oder wie schon erwähnt TObjectList statt TList verwenden, dann werden auch einzelne gelöschte Einträge ordentlich entsorgt.

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

Re: External SIGSEGV in TList beim Zählen der Listeneinträge

Beitrag von fliegermichl »

mse hat geschrieben:
fliegermichl hat geschrieben:und wenn dein Programm fertig ist, rufst du einfach MyList.Free auf und es werden auch alle Listeneinträge ordentlich entsorgt.

Oder wie schon erwähnt TObjectList statt TList verwenden, dann werden auch einzelne gelöschte Einträge ordentlich entsorgt.


TObjectList kannte ich jetzt noch gar nicht. Macht aber Sinn.

Antworten