Einzelnen Beitrag anzeigen

Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#2

Re: Umgang mit PChar - Speichermanagement

  Alt 26. Dez 2007, 10:14
Hi,

Zitat von cmrudolph:
Ich weiß bereits, dass der PChar Typecast nur verwendet werden kann, wenn ich eine damit gesetzte Variable nicht über den Funktionsrahmen hinaus verwenden möchte, weil der Speicher mit dem Beenden der Funktion wieder freigegeben wird. Somit fällt das also für die Rückgabe einer Funktion weg.
Wo hast Du denn die Information her? Also für einen Typecast mag das stimmen, kommt aber darauf an worauf Du den Typecast anwendenst. Was Du unterscheiden musst sind Variablen die auf dem Stack landen und solche, die auf dem Heap erzeugt werden. Auf dem Stack landen immer nur primitive Typen (sowas wie Integer, Cardinal, Double, ..., Char, Bool, String und Records). Der Stack ist ein einfacher Stapel, der jeder Funktion zur Verfügung steht. Die Funktionen wissen in welcher Reihenfolge Variablen dort abgelegt wurden und können die entsprechend runter nehmen (wie ein Tellerstapel, man kann immer nur das oberste Element runternehmen oder oben rauf legen). Das Besondere dabei ist, dass sich alle Funktionen den gleichen Stapel teilen, ruft also Funktion X die Funktion Y auf, so sichert X alle verwendeten Variablen in dem die erstmal auf dem Stack abgelegt werden. Y legt dann noch eigene rauf (ist jetzt alles etwas vereinfacht gesagt). Wichtig ist jedenfalls, dass wenn Y fertig ist, X weiterrechnen möchte. Dazu muss X also seine "alten" Variablen vom Stapel nehmen und mit denen Arbeiten. Hier muss also sichergestellt werden, dass die obersten Variablen genau die sind, die X abgelegt hat (Reihenfolge muss auch stimmen!). Das geht nur, wenn Y den Stapel so zurückgibt, wie er ursprünglich übergeben wurde.
Und genau das macht dann die lokalen Variablen lokal. Diese werden einfach auf dem Stack erzeugt und müssen mit dem Verlassen der Routine auch von dort entfernt werden. Entsprechend wird der belegte Speicher frei gegeben und Referenzen auf diesen Speicher wären ungültig.

Als Alternative steht dem Programmierer immer der Heap zur Verfügung. In dieser Datenstruktur werden beliebige Variablen verwaltet. Variablen trifft es nicht ganz, eigentlich müsste man eher von den Daten sprechen, die durch die Variablen referenziert werden. Jedenfalls steht der Heap quasi global zur Verfügung. Ein Datum, dass im Heap gespeichert wird muss entweder explizit frei gegeben werden (oder wird dies aut. beim Programmende, ist allerdings häufig eher unsauber).
Auf dem Heap wiederum landen Daten immer dann, wenn man Speicher alloziert. Implizit geschieht dies für alle Klassen, erzeugte Exemplare landen immer im Heap (und müssen auch mit Free freigegeben werden). Für Interfaces (bzw. die Klassen, die diese implementieren) sieht es dann ganz analog aus, allerdings kümmert sich die Referenzzählung dann selbstständig um die Freigabe.
Aber auch Speicher, den Du mit New oder GetMem allozierst landet auf dem Heap. Hier muss man dann wirklich etwas zwischen Variable und dem allozierten Speicher unterscheiden:

Delphi-Quellcode:
procedure doFoo;
var p: ^Integer;
begin
  p := New(Integer);
  p^ := 10;
  ...
end;
Hier würde jetzt p eine lokale Variable sein, der Zeiger p wird also nach dem Verlassen der Prozedur frei gegeben. Der allozierte Speicher auf den p zeigt(e) bleibt aber reserviert und enthält ein Integer mit dem Wert 10. Würdest Du die Adresse auf die p zeigt also an eine andere Routine weiterreichen, dann könnte auch diese (unabhängig davon ob doFoo beendet wurde) noch auf diesen Speicher zugreifen. Ganz wichtig, man muss auch sicherstellen, dass dieser Speicher irgendwann mal frei gegeben wird. Passiert zwar automatisch mit dem Programmende, aber bis dahin kann doFoo sehr oft aufgerufen werden (entsprechend viel Speicher wird ggf. einfach unerreichbar reserviert bleiben). Wird Speicher nicht frei gegeben und ist auch nicht mehr erreichbar, dann spricht man gerne von einem Speicherloch (macht den Rechner schön langsam und führte unter Win 9x auch gerne mal zu Fehlern wegen Mangel an Speicher, sollte natürlich nirgendwo überhaupt vorkommen! Gibt aber Memorymanager, die einen darauf hinweisen).

Ja, jedenfalls kannst Du so auch Speicher reservieren, der als PChar verwendet wird. Ein PChar ist nichts anderes als ein Zeiger auf einen Null-Terminierten String (nicht mit dem Delphi-String verwechseln!). Ein PChar speichert einfach nur die Adresse einer Speicherzelle. Hier steht das erste Zeichen, der String endet mit dem ersten vorkommen von chr(0). Wenn Du wiederum einen Delphi-String verwendest, entspricht das dem Delphi-AnsiString. Im Prinzip passiert hier das gleiche, zusätzlich reserviert Delphi jedoch noch Speicher für die Längeninformation (steht an einem negativen Offset vor dem ersten Zeichen). Hier findest Du auch die Begründung, warum StrAlloc als veraltet gilt, da hier explizit ein Zeiger auf ein AnsiChar bekommst. Statt mit diesen zu arbeiten, wird eben der Einsatz von AnsiString empfohlen, das im Prinzip auf gleiche Art und Weise funktioniert.

Das eigentliche Problem bei der Verwendung von Strings in DLLs liegt darin, dass Delphi den Speicher für Strings automatisch verwaltet, andere Sprachen dies jedoch nicht unbedingt tun. Da DLLs aber gerade aus verschiedenen Sprachen nutzbar sind, würde es hier zu interessanten Effekten bei der Mischung kommen.
Der sicherste Weg besteht deshalb einfach darin, dass Du entsprechend viel Speicher allozierst und den Ansi-String dorthin kopierst. Das ganze kannst Du ggf. auch in eine eigene Routine auslagern:

Delphi-Quellcode:
procedure StrToPChar(const s: String; const size: size_t; out p: PChar);
begin
  p := PChar(GetMem(length(s) * size_t + 1));
  p^[length(s) * size_t] := chr(0);
  copyMemory(s[1], p, length(s) * size_t]);
end;
Ist jetzt ungetestet. Gerade beim CopyMemory nochmal schaun, ich verwechsel da immer gerne Src und Dest in der Reihenfolge (klar, könnte mal in die Hilfe schauen...). Jedenfalls sollte das eigentlich klappen, man muss natürlich auch irgendwann p (bzw. den Speicher der durch p referenziert wird) wieder frei geben.

Gruß Der Unwissende
  Mit Zitat antworten Zitat