SIGSEGV beim Freigeben von Klasse

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

SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

Hallo Forum,

ich versuche mich weiter in der OOP und Klassen und so. Und ich verstehe gerade nicht, warum ich beim Freigeben eine Klasse rausfliege:

Der Code (die wesentlichen Teile) Teil 1 - Definintion:

Code: Alles auswählen

const
  TSignalPoint = class
    Time: double;
    Value: double;

    constructor Create; overload;
    constructor Create(t: double; v: double); overload;
  end;

  TTimeSeries = class
    Name: string;
    NoEntries: integer;
    Signal: TObjectList<TSignalPoint>;

    constructor Create;
    destructor Destroy; override;
    procedure SaveToFile(fname: string);
  end; 
  
  TTimeSeriesArray = Array of TTimeSeries; 

var
  BoltLTS: TTimeSeriesArray; 
  
// Class: TSignalPoint
// -------------------

// create generic signal point (0,0)
constructor TSignalPoint.Create;
begin
  inherited;
  Time := 0.0;
  Value := 0.0;
end;

// create signal point from time and value pair
constructor TSignalPoint.Create(t: double; v: double);
begin
  Time := t;
  Value := v;
end;

// Class: TTimeSeries
// ------------------

// create empty timeseries
constructor TTimeSeries.Create;
begin
  inherited;
  Name := '';
  NoEntries := 0;
  Signal := TObjectList<TSignalPoint>.Create;
end;

destructor TTimeSeries.Destroy;
begin
  Signal.Free;
  inherited Destroy;
end;

Teil 2 - Verwendung:

Code: Alles auswählen

     try
        BoltLTS := Read_TimeSeries_MultiChannel(FullFileName);

        LogMemo.Append('  LTS-file loaded');
        LogMemo.Append(Format('   No Bolts: %d ',[Length(BoltLTS)]));

        // Output of some LTS parameter
        for i:=0 to Length(BoltLTS)-1 do
          LogMemo.Append(Format('   Bolt: %d ("%s"): %d',
            [i,BoltLTS[i].Name,BoltLTS[i].NoEntries]));

        for i:=0 to Length(BoltLTS)-1 do
          processTimeSeries(BoltLTS[i]);
      finally
        for i:=0 to Length(BoltLTS)-1 do
        begin
          LogMemo.Append(Format('   Free Bolt: %d ("%s")',
            [i,BoltLTS[i].Name]));
          BoltLTS[i].Free;    // <---- genau das funktioniert nicht
        end;
        SetLength(BoltLTS,0);
     end;
     
An der markierten Stelle bekomme ich einen SIGSEGV und lande im Assembler-Fenster (Abschnittüberschrift: "SYSTEM$_$TOBJECT_$__$$_FREE (13)", vielleicht sagt das ja jemandem was). Natürlich könnte ich das Free erstmal weglassen und das Speicherleck in Kauf nehmen.

Es könnte was einfaches sein; es könnte auch sein, dass ich einfach noch was nicht richtig verstanden habe (Constructor, Destructor mit inherited). Auch im Zusammenspiel mit Generics und dem Array of ....

Dankbar für jeden Tipp.
Photor

PS: wenn gebraucht, kann ich versuchen ein abgespecktes Projekt zusammenstellen.

PascalDragon
Beiträge: 293
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: SIGSEGV beim Freigeben von Klasse

Beitrag von PascalDragon »

Was macht denn dein Read_TimeSeries_MultiChannel?

Randnotiz: du kannst in den for-Schleifen auch High(BoltLTS) statt Length(BoltLTS)-1 verwenden (High gibt immer den höchsten Index eines Arrays zurück, egal ob dynamisch oder statisch; bei einem leeren Array ist das dann -1).
FPC Compiler Entwickler

charlytango
Beiträge: 333
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.0 fixes FPC 3.2 fixes
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von charlytango »

hi
bin kein OOp Hero (auch wenn ich mich jetzt blamiere), aber mir fehlt irgendwie die Erstellung des Objektes BoltLTS.

Also etwas in der Art wie

Code: Alles auswählen

procedure EinTest;
var
  BoltLTS:TDeineKlasse;

begin
  BoltLTS:=TDeineKlasse.Create;
  try
     //Mach was mit dem erstellten Objekt
     BoltLTS.Read_TimeSeries_MultiChannel(FullFileName);
  finally
   //und sorge für das Zerstören und Freigeben
   freeandnil(BoltLTS);
   
   //wenn ich es nicht ganz genau weiß 
   //und ich es testen will dann etwas wie das:
   {if assigned(BoltLTS) then
      freeandnil(BoltLTS)
   else
     showmessage('OOPs.. Das Objekt existiert hier nicht (mehr)');
   }
  end;
end;  
  


wenn ich davon ausgehe dass

Code: Alles auswählen

BoltLTS := Read_TimeSeries_MultiChannel(FullFileName);
irgend etwas mit der Objekterstellung zu tun hat würde ich es anders designen.
Zuerst Objekt erstellen, dann irgendwas damit anstellen.

Teste doch mal mit if assigned() ob das Objekt zu diesem Zeitpunkt überhaupt existiert.
Könnte sein dass dein Code eine Exception wirft, sich im finally Teil wiederfindet und dann versucht ein nicht existierendes Objekt zu zerstören.

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

PascalDragon hat geschrieben:
Sa 20. Mär 2021, 15:18
Was macht denn dein Read_TimeSeries_MultiChannel?
Stimmt. Du hast recht. Kommt hier:

Code: Alles auswählen

// read TimeSeries-MultiChannel file
function Read_TimeSeries_MultiChannel(fname: string): TTimeSeriesArray;
var
  i, j, NoTimeSeriesInFile: integer;
  time, value: double;
  str:string;
  sp: TSignalPoint;
  FileData: TStringList;
  LineData: TStringList;
begin
  // to hold the file in memory
  FileData := TStringList.Create;
  FileData.Clear;

  // keep and process the line
  LineData := TStringList.Create;
  LineData.Clear;
//  LineData.Delimiter := #9;

  try
    FileData.LoadFromFile(fname);     // load file in FileData-StringList

    // 1st line contains names of the columns
    LineData.DelimitedText := FileData[0];

    NoTimeSeriesInFile := LineData.Count - 1; // starting with 0; 1st col is Time

    SetLength(result, NoTimeSeriesInFile);

    // create all TTimeSeries
    for i:=0 to NoTimeSeriesInFile-1 do
    begin
      result[i] := TTimeSeries.Create;
      result[i].Name := LineData[i+1];              // Name of TimeSeries in 1st line of file
      result[i].NoEntries := FileData.Count - 1;    // number of lines-1 in file = length of all TimeSeries
    end;

    // process all lines of file
    for i:=1 to FileData.Count-1 do
    begin
      LineData.DelimitedText := FileData[i];    // split the line

      time := StrToFloat(LineData[0]);

      // distribute over time series
      for j:=0 to NoTimeSeriesInFile-1 do
      begin
        value := StrToFloat(LineData[j+1]);

        sp := TSignalPoint.Create;
        sp.Time := time;
        sp.Value := value;

        result[j].Signal.Add(sp);
      end;
    end;
  finally
    FileData.Free;
    LineData.Free;
  end;
end;
PascalDragon hat geschrieben:
Sa 20. Mär 2021, 15:18
Randnotiz: du kannst in den for-Schleifen auch High(BoltLTS) statt Length(BoltLTS)-1 verwenden (High gibt immer den höchsten Index eines Arrays zurück, egal ob dynamisch oder statisch; bei einem leeren Array ist das dann -1).
Den Verdacht wg der Indizierung hatte ich auch schon. Aber ich hab's getestet: es passiert direkt beim aller ersten Eintrag - so als wäre das Array schon leer.

Ciao,
Photor

charlytango
Beiträge: 333
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.0 fixes FPC 3.2 fixes
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von charlytango »

ich versteh immer noch nicht warum

Code: Alles auswählen

function Read_TimeSeries_MultiChannel(fname: string): TTimeSeriesArray;
keine Funktion der Klasse TTimeSeries ist und frei irgendwo herumliegt.

Zumal es in der Klasse ja auch eine

Code: Alles auswählen

procedure SaveToFile(fname: string);
gibt.

die Funktion Read_TimeSeries_MultiChannel macht ja auch nichts anderes als Daten von einem File einzulesen.

ich würde das so lösen:

Code: Alles auswählen

TTimeSeries = class
    Name: string;
    NoEntries: integer;
    Signal: TObjectList<TSignalPoint>;

    constructor Create;
    destructor Destroy; override;
    procedure SaveToFile(fname: string);

    //die angepasste Funktion Read_TimeSeries_MultiChannel, die 
    //True bei erfolgreichem Einlesen zurückgibt
    function ReadFromFile(fname: string):boolean; 
end; 

und dann

Code: Alles auswählen

procedure EinTest;
var
  BoltLTS:TTimeSeries ;
begin
  BoltLTS:=TDeineKlasse.Create;
  try
     //Mach was mit dem erstellten Objekt
     if BoltLTS.ReadFromFile(FullFileName) then begin
         //mach mehr mit dem Objekt
     else
       showmessage('Fehler beim Lesen der Daten vom File');
  finally
   //und sorge für das Zerstören und Freigeben
    freeandnil(BoltLTS);
  end;
end;


Dann erstellst du das Objekt genau dort wo du es benutzt und nicht irgendwo im Code.
Und mit diesen Rahmenbedingungen kannst du dich auf die Suche nach Fehlern begeben.
Es wäre sicher auch hilfreich wenn du mal sagst was du mit dem Code eigentlich grob machen möchtest. Was ist Ziel der ganzen Aktion?

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von hum4n0id3 »

Ich vermute stark der TE möchte sich mehr mit OOP befassen. Das ist gut und richtig.

Ich habe den obigen Code mehrmals hin und her geschaut und gesucht und sehe schwer durch. Den die Objekte nicht irgendwo erzeugen, sondern dort wo diese gebraucht werden. So wie der Code jetzt ist, schätze ich ihn immer noch als Prozedural ein.

Was soll genau erreicht werden?

Warf
Beiträge: 1561
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: MacOS | Win 10 | Linux
CPU-Target: x86_64
Wohnort: Aachen

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von Warf »

Vermuten würde ich einen Double-Free Fehler, sehen tu ich aber keinen. Können eventuell datenpunkte zwischen den listen geshared sein sodass bereits eine vorrige liste die daten gelöscht hat die noch in einer anderen Liste waren?

Ich kann dir für sowas debugging mit Valgrind empfehlen, das kann dir mehr infos über Speicherfehler geben. Z.b. wenn es ein double free ist, sagt dir valgrind genau wo das erste free stattgefunden hat, bei einem Use-After-Free sagt dir valgrind wo das Free war und reported bereits schon den fehler sobald du versuchst drauf zuzugreifen, nicht nur wenn es knallt.

Ein allgemeiner vorschlag:

Code: Alles auswählen

  TSignalPoint = class
    Time: double;
    Value: double;

    constructor Create; overload;
    constructor Create(t: double; v: double); overload;
  end;
Du benutzt hier eine klasse ohne virtuelle funktionen und (wahrscheinlich) ohne RTTI für 16 byte an Daten. Der overhead von Klassen instanzen ist 8 byte (ein pointer auf die RTTI/VMT Datenstruktur des Typen) und da Klassen über Pointer referenziert werden stehen in deiner TObjectList 8 byte große pointer drin die auf die daten zeigen.
Du hast also um 16 byte zu speichern weitere 16 byte pro instanz overhead, und nutzt kein einziges der Features von Klassen. Benutz doch einfach einen Record und eine generics.Collections.TList<RecordType> und spar dir das ein. Vom Speicher abgesehen, gehen klassen beim Create und Destroy durch den Heap Manager, da sie eine eigene Heap allokation brauchen, was nicht nur verdammt langsam ist, sondern auch noch mal speicher overhead hat.

Mal ganz davon abgesehen das falls es sich bei deinem Fehler um ein double free oder use after free in den Datenpunkten handelt, du das damit auch komplett umgehen kannst

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

Hallo,

Vielen Dank. Das ist ja einiges an Input. Das muss ich erstmal durchschauen, durchblicken und verstehen. Da geht bestimmt der Rest des Wochenendes bei drauf.

Der Tipp mit Valgrind: damit hab ich mich noch gar nicht beschäftigt. Werde ich aber, weil klingt gut. Ich glaube auch, dass Speicher schon freigegeben ist, wenn ih im finally vorbei komme. Ich seh halt nicht, wo.

Und ja: mit OOP will ich mich mehr beschäftigen, aber denken tue ich wohl noch zu prozedural.

Ich melde mich bestimmt wieder.

Ciao,
Photor

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

charlytango hat geschrieben:
Sa 20. Mär 2021, 16:44
ich versteh immer noch nicht warum

Code: Alles auswählen

function Read_TimeSeries_MultiChannel(fname: string): TTimeSeriesArray;
keine Funktion der Klasse TTimeSeries ist und frei irgendwo herumliegt.

Zumal es in der Klasse ja auch eine

Code: Alles auswählen

procedure SaveToFile(fname: string);
gibt.

die Funktion Read_TimeSeries_MultiChannel macht ja auch nichts anderes als Daten von einem File einzulesen.

ich würde das so lösen:

Code: Alles auswählen

TTimeSeries = class
    Name: string;
    NoEntries: integer;
    Signal: TObjectList<TSignalPoint>;

    constructor Create;
    destructor Destroy; override;
    procedure SaveToFile(fname: string);

    //die angepasste Funktion Read_TimeSeries_MultiChannel, die 
    //True bei erfolgreichem Einlesen zurückgibt
    function ReadFromFile(fname: string):boolean; 
end; 

und dann

Code: Alles auswählen

procedure EinTest;
var
  BoltLTS:TTimeSeries ;
begin
  BoltLTS:=TDeineKlasse.Create;
  try
     //Mach was mit dem erstellten Objekt
     if BoltLTS.ReadFromFile(FullFileName) then begin
         //mach mehr mit dem Objekt
     else
       showmessage('Fehler beim Lesen der Daten vom File');
  finally
   //und sorge für das Zerstören und Freigeben
    freeandnil(BoltLTS);
  end;
end;


Dann erstellst du das Objekt genau dort wo du es benutzt und nicht irgendwo im Code.
Und mit diesen Rahmenbedingungen kannst du dich auf die Suche nach Fehlern begeben.
Es wäre sicher auch hilfreich wenn du mal sagst was du mit dem Code eigentlich grob machen möchtest. Was ist Ziel der ganzen Aktion?
Danke für den Vorschlag. Ich hatte das als Extra-Funktion deklariert, weil in dem File, dass da gelesen wird nicht nur eine TimeSeries gespeichert ist, sondern gleich mehrere. Daher auch die Rückgabe eines Arrays.

Zum Zweck des ganze: ich will mehrere Zeitreihen (z.B. Messungen oder Simulationen) einlesen und diese weiter verarbeiten (am Ende soll ein Rainflow-Counting stehen). Die Messungen eines Sensors bestehen üblicherweise aus mehreren Komponenten (typisch 6 für 3 Kräfte und 3 Momente) die zusammengefasst in Spalten geliefert werden (1 Spalte = Zeit, dann alle Komponenten) - das Format heißt Multi-Channel: in der 1. Zeile stehen die Spaltennamen ("Time", "Fx", "Fy", "Fz", "Mx", "My", "Mz").

Also würde ich das Einlesen dieses Files eher losgelöst von der (einzelnen) Zeitreihe betrachten; daher außerhalb der Klasse TimeSeries und die Definition eines Array-Typs (TTimeseriesArray) als Rückgabe der Einlesefunktion. Anschließend werden - Stand jetzt - die TimeSeries getrennt weiter verarbeitet (z.B. gefiltert) - und eventuell gespeichert; daher die Funktion SaveToFile in der Klasse .

Soviel vielleicht erstmal zum Zweck des ganzen.

Ciao,
Photor

charlytango
Beiträge: 333
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.0 fixes FPC 3.2 fixes
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von charlytango »

hmmmm....

kann mich ja irren, aber für mich sieht das so aus als ob du versuchst so etwas wie eine Tabelle zu erstellen in die du die Daten einliest um sie dann irgendwie weiter zu verarbeiten.
Sollte das zutreffen und du es des Ergebnisses wegen machst (und nicht als Programmierübung) bist du möglicherweise besser bedient mit bereits bestehenden Komponenten in Lazarus:

In der Komponentenleiste unter dem Tab "Data Access" findest du TMemDataset und seine Verwandten in die du zB deine Daten in eine InMemory-Tabelle einliest und vielleicht gleich in einem TChart anzeigst.
Ist nur so eine schräge Idee.... ;)

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von af0815 »

wenn du im objekt ein destroy hast, setze mal einen breakpoint hinein und schau dann nach, ob es dort wo du es erwartest freigegeben wird, mittels callstack.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

PascalDragon
Beiträge: 293
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: SIGSEGV beim Freigeben von Klasse

Beitrag von PascalDragon »

photor hat geschrieben:
Sa 20. Mär 2021, 15:31
PascalDragon hat geschrieben:
Sa 20. Mär 2021, 15:18
Was macht denn dein Read_TimeSeries_MultiChannel?
Stimmt. Du hast recht. Kommt hier:

Code: Alles auswählen

// read TimeSeries-MultiChannel file
function Read_TimeSeries_MultiChannel(fname: string): TTimeSeriesArray;
var
  i, j, NoTimeSeriesInFile: integer;
  time, value: double;
  str:string;
  sp: TSignalPoint;
  FileData: TStringList;
  LineData: TStringList;
begin
  // to hold the file in memory
  FileData := TStringList.Create;
  FileData.Clear;

  // keep and process the line
  LineData := TStringList.Create;
  LineData.Clear;
//  LineData.Delimiter := #9;

  try
    FileData.LoadFromFile(fname);     // load file in FileData-StringList

    // 1st line contains names of the columns
    LineData.DelimitedText := FileData[0];

    NoTimeSeriesInFile := LineData.Count - 1; // starting with 0; 1st col is Time

    SetLength(result, NoTimeSeriesInFile);

    // create all TTimeSeries
    for i:=0 to NoTimeSeriesInFile-1 do
    begin
      result[i] := TTimeSeries.Create;
      result[i].Name := LineData[i+1];              // Name of TimeSeries in 1st line of file
      result[i].NoEntries := FileData.Count - 1;    // number of lines-1 in file = length of all TimeSeries
    end;

    // process all lines of file
    for i:=1 to FileData.Count-1 do
    begin
      LineData.DelimitedText := FileData[i];    // split the line

      time := StrToFloat(LineData[0]);

      // distribute over time series
      for j:=0 to NoTimeSeriesInFile-1 do
      begin
        value := StrToFloat(LineData[j+1]);

        sp := TSignalPoint.Create;
        sp.Time := time;
        sp.Value := value;

        result[j].Signal.Add(sp);
      end;
    end;
  finally
    FileData.Free;
    LineData.Free;
  end;
end;
Anmerkungen:
- der Aufruf von Clear bei den Stringlisten ist nach dem Erzeugen nicht nötig, da sie eh leer sind
- und selbst wenn du Clear nutzt, solltest du jeden Methodenaufruf auf FileData und LineData mit dem Resourceschutzblock versehen:

Code: Alles auswählen

  FileData := TStringList.Create;
  try
    // das Clear ist wie gesagt unnötig, aber zur Verdeutlichung
    FileData.Clear;

    // wenn man weiß wie, kann man diese Schachtelung noch etwas
    // optimieren, aber das hier ist das Grundbeispiel
    LineData := TStringList.Create;
    try
      LineData.Clear;
      
      // ...
    finally
      LineData.Free;
    end;
  finally
    FileData.Free;
  end;
Beide Punkte haben jedoch nichts mit deinem Problem zu tun. Kannst du mal auch den Code von processTimeSeries zeigen oder dessen Ausruf mal auskommentieren und dann probieren?

Außerdem könntest du mal die folgenden Optionen unter Projekteinstellungen -> Compilereinstellungen -> Debuggen aktivieren:
  • Bereich (-Cr)
  • Überlauf (-Co)
  • Methodenaufrufe prüfen (-CR)
photor hat geschrieben:
Sa 20. Mär 2021, 15:31
PascalDragon hat geschrieben:
Sa 20. Mär 2021, 15:18
Randnotiz: du kannst in den for-Schleifen auch High(BoltLTS) statt Length(BoltLTS)-1 verwenden (High gibt immer den höchsten Index eines Arrays zurück, egal ob dynamisch oder statisch; bei einem leeren Array ist das dann -1).
Den Verdacht wg der Indizierung hatte ich auch schon. Aber ich hab's getestet: es passiert direkt beim aller ersten Eintrag - so als wäre das Array schon leer.
Meine Anmerkung hatte nichts mit deinem Problem zu tun. Es ging darum dass das sinnvoller ist.

Noch eine Anmerkung zu deiner Hauptfunktion: du musst das BoltLTS-Array in deinem finally nicht auf 0 setzen, da dynamische Arrays automatisch zurückgesetzt werden (das Free für die Objekte ist jedoch nötig).
FPC Compiler Entwickler

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

PascalDragon hat geschrieben:
So 21. Mär 2021, 12:14
Anmerkungen:
- der Aufruf von Clear bei den Stringlisten ist nach dem Erzeugen nicht nötig, da sie eh leer sind
- und selbst wenn du Clear nutzt, solltest du jeden Methodenaufruf auf FileData und LineData mit dem Resourceschutzblock versehen:

Code: Alles auswählen

  FileData := TStringList.Create;
  try
    // das Clear ist wie gesagt unnötig, aber zur Verdeutlichung
    FileData.Clear;

    // wenn man weiß wie, kann man diese Schachtelung noch etwas
    // optimieren, aber das hier ist das Grundbeispiel
    LineData := TStringList.Create;
    try
      LineData.Clear;
      
      // ...
    finally
      LineData.Free;
    end;
  finally
    FileData.Free;
  end;
Jo, mach ich.
PascalDragon hat geschrieben:
So 21. Mär 2021, 12:14
Beide Punkte haben jedoch nichts mit deinem Problem zu tun. Kannst du mal auch den Code von processTimeSeries zeigen oder dessen Ausruf mal auskommentieren und dann probieren?

Außerdem könntest du mal die folgenden Optionen unter Projekteinstellungen -> Compilereinstellungen -> Debuggen aktivieren:
  • Bereich (-Cr)
  • Überlauf (-Co)
  • Methodenaufrufe prüfen (-CR)
Das hab ich schon so eingestellt gehabt.
PascalDragon hat geschrieben:
So 21. Mär 2021, 12:14
Meine Anmerkung hatte nichts mit deinem Problem zu tun. Es ging darum dass das sinnvoller ist.

Noch eine Anmerkung zu deiner Hauptfunktion: du musst das BoltLTS-Array in deinem finally nicht auf 0 setzen, da dynamische Arrays automatisch zurückgesetzt werden (das Free für die Objekte ist jedoch nötig).
Das waren schon Versuche, den Fehler bzw. Memory-Leaks einzukreisen.

Dann noch der Code zu:

Code: Alles auswählen

procedure TMainForm.ProcessTimeSeries(ts: TTimeSeries);
var
  fname: string;
  TS_minmax: TTimeSeries;
begin
  try
     // find min/max
    TS_minmax := Peak_n_Valley(ts);

    fname := TS_minmax.Name + '.txt';

    TS_minmax.SaveToFile(fname);
  finally
    TS_minmax.Free;
  end;
end;
Diese Prozedure sucht (im Moment nur nach Minima und Maxima in der übergebenen (einzelnen) TimeSeries, speichert die entsprechenden Signal-Zeitpunkte in einer neuen TimeSeries TS_minmax, die dann gespeichert wird. Zum Schluss wird die TS_minmax freigegeben (im weiteren Verlauf soll hier natürlich noch mehr mit passieren - also müsste ich die eigentlich auch wieder als Ergebnis zurück geben; das war aber für jetzt zu kompliziert).

... und dann noch (in einer eigenen Unit sb_defs.pas):

Code: Alles auswählen

function Peak_n_Valley(ts: TTimeSeries) : TTimeSeries;
var
  i: integer;
  spu, sp, spo: TSignalPoint;
begin
  result := TTimeSeries.Create;

  // add 1st data point to list
  sp := ts.Signal.First;
  result.Signal.Add(sp);

  // look for relative min and max in time series
  for i:=1 to ts.Signal.Count-2 do
  begin
    spu := ts.Signal.Items[i-1];
    sp := ts.Signal.Items[i];
    spo := ts.Signal.Items[i+1];

    if is_minmax(spu, sp, spo) then
      result.Signal.Add(sp);
  end;

  // add last data point
  sp := ts.Signal.Last;
  result.Signal.Add(sp);
  result.Name := ts.Name;

  // set number of entries in resulting TimeSeries
  result.NoEntries := result.Signal.Count;
end;

 function is_minmax(dpu, dp, dpo: TSignalPoint): boolean;
var
  diff1, diff2: double;
begin
  result := false;

  diff1 := dp.Value - dpu.Value;
  diff2 := dpo.Value - dp.Value;

  if (((diff1 > 0.0) and (diff2 < 0.0)) or         // rel. maximum
      ((diff1 < 0.0) and (diff2 > 0.0))) then      // rel. minimum
    result := true
end;  
Wenn ich mir den Aufruf-Stack nach dem SIGSEGV ansehe:

Code: Alles auswählen

#0 ?? at :0
#1 SYSTEM$_$TOBJECT_$__$$_FREE at :0
#2 ?? at :0
#3 NOTIFY(0xcbaa18, 0xe1c2a8, CNREMOVED) at generics.collections.pas:2221
#4 DELETERANGE(0xcbaa18, 0, 1442) at generics.collections.pas:1639
#5 SETCOUNT(0xcbaa18, 0) at generics.collections.pas:1431
#6 SETCAPACITY(0xcbaa18, 0) at generics.collections.pas:1418
#7 DESTROY(0xcbaa18, 0x1) at generics.collections.pas:1412
#8 SYSTEM$_$TOBJECT_$__$$_FREE at :0
#9 ?? at :0
#10 DESTROY(0xe1c238, 0x1) at sb_defs.pas:99
#11 SYSTEM$_$TOBJECT_$__$$_FREE at :0
#12 ?? at :0
#13 SELLTSBTNCLICK(0xbcf2d8, 0xbd22f8) at sb_main.pas:156
#14 CONTROLS$_$TCONTROL_$__$$_CLICK at :0
#15 ?? at :0
#16 STDCTRLS$_$TBUTTONCONTROL_$__$$_DOONCHANGE at :0
#17 ?? at :0
#18 STDCTRLS$_$TBUTTONCONTROL_$__$$_CLICK at :0
#19 ?? at :0
#20 STDCTRLS$_$TCUSTOMBUTTON_$__$$_CLICK at :0
#21 ?? at :0
... passiert das Unglück letztendlich in Generics.Collection (von sb_main.pas über sb_defs.pas kommend).

Ciao,
Photor

Warf
Beiträge: 1561
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: MacOS | Win 10 | Linux
CPU-Target: x86_64
Wohnort: Aachen

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von Warf »

photor hat geschrieben:
So 21. Mär 2021, 17:13
Wenn ich mir den Aufruf-Stack nach dem SIGSEGV ansehe:

Code: Alles auswählen

#0 ?? at :0
#1 SYSTEM$_$TOBJECT_$__$$_FREE at :0
#2 ?? at :0
#3 NOTIFY(0xcbaa18, 0xe1c2a8, CNREMOVED) at generics.collections.pas:2221
#4 DELETERANGE(0xcbaa18, 0, 1442) at generics.collections.pas:1639
#5 SETCOUNT(0xcbaa18, 0) at generics.collections.pas:1431
#6 SETCAPACITY(0xcbaa18, 0) at generics.collections.pas:1418
#7 DESTROY(0xcbaa18, 0x1) at generics.collections.pas:1412
... passiert das Unglück letztendlich in Generics.Collection (von sb_main.pas über sb_defs.pas kommend).

Ciao,
Photor
Notify ist der mechanismus von TList<T> um die elemente zu löschen:

Code: Alles auswählen

procedure TObjectList<T>.Notify(constref AValue: T; ACollectionNotification: TCollectionNotification);
begin
  inherited Notify(AValue, ACollectionNotification);

  if FObjectsOwner and (ACollectionNotification = cnRemoved) then
    TObject(AValue).Free;
end; 
Also würde ich ziemlich stark auf meine erste vermutung double-free von einem der elemente tippen. Irgendwo löschst du das element bereits schon vorher (evtl wenn es zwischen listen geshared ist).

Wie gesagt, wenn du records benutzt löst sich das Problem von selbst, und das würde ich dir immernoch empfehlen, manuelle Speicherverwaltung ist schwer, und je mehr man es umgehen kann (z.b. durch das verwenden von records) desto besser.

Wenn dich wirklich interessiert wo der bug her kommt, hier mal eine kurze Erklärung zu valgrind. Valgrind ist ein Program um Programmausführungen zu überwachen. Das kann man praktisch für alles benutzen, z.B. könnte man damit zählen wie oft ein Integer die Zahl 42 annimmt, allerdings ist die standardfunktionalität für die valgrind auch bekannt ist die speicheranalyse. Dafür überwacht valgrind jede speicher operation und so zu sagen führt liste darüber. Wenn du jetzt einen Speicherzugriff hast schaut valgrind also nach ob 1. das objekt jemals existiert hat und 2. ob das objekt noch am leben ist. Falls nicht kann es dir genau sagen wann und wo es gestorben ist. Außerdem kann valgrind memory leaks detektieren da es ja über alle speicherallokationen buch führt. Es ist also so zu sagen eine ausführlichere version der heaptrc unit, es kann auch buffer overflows detecten und erkennen wenn man mit uninizialisierten werten arbeitet, ist also ein all-around tool um alle möglichen arten von Speicherfehlern zu entdecken. Allerdings, sei gewarnt, Valgrind als mittelprächtiger emulator bedeutet einen faktor 100-1000 an overhead was ausführungszeit angeht. Also solltest du besser deine tests reduzieren (z.b. kleine Datensätze verwenden) und/oder viel zeit mitbringen wenn dein code bereits schon eh sehr zeitaufwendig ist.

Valgrind hab ich bisher nur unter Linux verwendet, es gibt es aber auch für windows, allerdings weiß ich nicht wie der FPC damit umgehen kann (da für valgrind soweit ich weiß gegen die libc gelinkt werden muss, und das kann der FPC glaube ich nur unter Linux). Um valgrind zu benutzen musst du im Debugging Tab der Projektoptionen gehen und dort "Generate Code for Valgrind (-gv)" aktivieren. Dann um das ganze in valgrind auszuführen dann einfach in der Konsole:

Code: Alles auswählen

$> valgrind [--leak-check=full] ./deinprogram [argumente]
Und Valgrind gibt alle Fehlermeldungen in der Konsole aus.


PS: was mir auch grade aufgefallen ist, du scheinst ein großer Fan davon zu sein Objekte als rückgabewert von funktionen zu verwenden, zwar finde ich das an sich auch schöner als die verwendung von parametern, aber hier musst du mit exceptions aufpassen. Aus deinem code:

Code: Alles auswählen

      for j:=0 to NoTimeSeriesInFile-1 do
      begin
        value := StrToFloat(LineData[j+1]);

        sp := TSignalPoint.Create;
        sp.Time := time;
        sp.Value := value;

        result[j].Signal.Add(sp);
      end;
StrToFloat kann eine exception werfen. Wenn du die exception an der callsite fängst (also wo Read_TimeSeries_MultiChannel aufgerufen wird) oder sogar höher im aufrufstack ist das ergebnis der Funktion kaputt, d.h. das alle objekte die in deren array gelebt haben sind verloren.
An solchen stellen wäre dann solcher code nötig:

Code: Alles auswählen

try
  // Code der eine exception werfen kann
except on E: Exception do
  for obj in Result do
    obj.Free;
  raise E; // Exception weiter durchreichen
end;
um im Falle einer Exception keine speicherlücken zu generieren

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

Re: SIGSEGV beim Freigeben von Klasse

Beitrag von photor »

Danke schon mal hier Warf. Das werde ich mir in Ruhe nochmal ansehen.

An records hatte ich auch schon gedacht (weil auch bisher mehr genutzt). Aber man will ja auch was neues lernen.

Ich werde weiter schauen und probieren. Morgen. Heute ist Rest von WE.

Ciao,
Photor

Antworten