Autor Beitrag
tommie-lie
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 4373

Ubuntu 7.10 "Gutsy Gibbon"

BeitragVerfasst: So 27.07.03 20:01 
Dynamische Arrays als Funktionsrückgaben ohne Speicherlecks

Wenn eine Funktion ein dynamisches Array zurückgibt, die aus eingegebenen Daten gefüllt werden, muss die Länge des Arrays sehr oft vergrößert werden.
Da dies nicht sehr speichereffektiv ist, weil der Speichermanager den alten Speicher des Arrays nicht wieder freigibt, wenn es in den vorhanden Speicherblock nicht mehr hineinpasst, kann man Alternativen verwenden. Neben einem eigenen Speichermanager wäre die sehr viel einfachere Möglichkeit die Benutzung einer verketteten Liste, um die Zwischenergebnisse effektiv zu speichern.
Angenommen das zurückgegebene Array soll ein Integer-Array sein:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
type
  TResultsArray: array of Integer;
function FillArray: TResultsArray;
type
  PResultItem = ^TResultItem;
  TResultItem = record
    val: Integer;
    next: PResultItem;
  end;
var
  Results: PResultItem;
  NewResult: PResultItem;  
  Count: Integer;

next zeigt auf den jeweils nächsten Listeneintrag, val enthält die Nutzdaten. Neben Integer sind hier natürlich beliebige Typen erlaubt, sogar andere Records oder Objekte (für diese muss jedoch getrennt Speicher alloziiert und wieder freigegeben werden).
Results ist Repräsentant für die gesamte Liste an Ergebnissen. Wenn val ein ganzzahliger Typ ist, kann man diesen Eintrag beim Results-Record auch als Counter verwenden, der anzeigt, wieviele Einträge die Liste insgesamt hat. Hier verwende ich jedoch eine dritte Variable "Count".

Die Liste muss natürlich initialisiert werden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
  // initialize the mini-list
  New(SearchResults);
  SearchResults.val := 0;
  SearchResults.next := nil;
  Count := 0;


Für jeden Eintrag, den man jetzt dem Ergebnisarray zuweisen will, tut man stattdessen folgendes:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
  New(NewResult);
  NewResult.next := Results.next;
  Results.next := NewResult;
  NewResult.val := DesiredValue; {hier wird der Wert zugewiesen!!!}
  inc(Count);

Die Liste speichert also die Ergebnisse rückwärts. Der erste Eintrag in der Liste (Results.next) enthält tatsächlich den zuletzt hinzugefügten Eintrag.

Ist man mit seinen Berechnungen/Auswertungen fertig, muss natürlich das Array, welches ja zurückgegeben wird, gefüllt werden und die Liste wieder gelöscht werden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
  // now, set the (dynamic) result array to the right length in one go
  SetLength(result, Count);
  // iterate through every item in the list, set the array's value and dispose
  // the memory at the same time
  while Results.next <> nil do
  begin
    NewResult := Results;
    Results := Results.next;
    result[Count - 1] := Results.val;
    dec(Count);
    Dispose(NewResult);
  end;
  Dispose(Results);

Hier wurde NewResult entgegen seinem Namen ein wenig zweckentfremdet, aber die kann man ja nennen wie man will, und der Hauptsinn der Variable ist nunmal, einen neuen Eintrag hinzuzufügen.

Da in der gesamten Funktion SetLength nur einmal aufgerufen wurde und jeder Eintrag aus der Liste wieder freigegeben wird, entstehen auf diese Weise keine Speicherlecks.
Das Verfahren lässt sich natürlich nicht nur bei Funktionen anwenden, die ein dynamisches Array zurückgeben, sondern überall, wo ein Array oft verändert werden muss und man später in einem Array auf die Daten zugreifen will/muss.

Moderiert von user profile iconjasocul: Beitrag geprüft am 08.06.2006

_________________
Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert