|
Antwort |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#1
Oft wird Delphi-Neulingen geraten, bei der Arbeit mit Objekten die Referenzen nach der Freigabe eines Objekts (zB mit FreeAndNil) wieder auf nil zu setzen, damit ein Vergleich der Form
if Assigned(AnObject) then eingesetzt werden kann, um zu überprüfen, ob es sich um eine gültige Referenz handelt. Der Compiler von Delphi initialisiert Objektreferenzen innerhalb von Methoden und Prozeduren/Funktionen nicht vor, so dass ein Test der Art
Delphi-Quellcode:
zwar überprüft, ob die Referenz (hier: myObject) einen Wert hält, der von nil verschieden ist, kann aber im Fall einer "durch Zufall" von nil verschiedenen "Vorbelegung" nicht entscheiden, ob der Wert nun tatsächlich auf ein Objekt verweist.
procedure MyProc;
var myObject: TObject; begin if Assigned(myObject) then Innerhalb des DEC und der JCL konnte ich nun zwei verschiedene Implementierungen einer Funktion IsObject (bei der JCL auch eine IsClass für Klassenreferenzen) finden, die zwei unterschiedliche Ansätze verfolgen. Die Lösung von Hagen prüft, sofern ich sie nicht missdeute, ob vor dem Speicher, in dem das vermeintliche Objekt liegt, einen MagicCode enthält (genauer ein Bit), der vom Borland-Speichermanager dort hinterlegt worden sein sollte. Die Lösung innerhalb der JCL geht davon aus, dass ein Objekt zu einer Klasse gehört und überprüft innerhalb der VMT der Klasse, ob die dort erwartete Referenz auf die Klasse selbst vorhanden ist. Während Hagens Ansatz schneller arbeiten sollte kann bei zufälligen Werten (durch eine zufällig vorbelegte lokale Referenz) durch seine Maskierung der Form if MagicCode and $2 = $2 then nur zu 50% gesagt werden, ob es sich um ein Objekt handelt. Aus informationstheoretischer Sicht ist dies keine Information, also nutzlos für diesen Fall, obgleich die Routine für andere Szenarien korrekt arbeiten sollte. Darüber hinaus könnte dieser Ansatz nicht mehr funktionieren, wenn man die Speichervergabe von Objekten über eine eigene Implementierung (NodeManager) lösen würde, die ohne den MagicCode auskommt. Der Ansatz der JCL prüft zwar ein wesentlich unwahrscheinlicheres Kriterium, hat aber gegenüber der Lösung aus dem DEC den Nachteil, das er mit diversen Indirektionen arbeitet, also im Speicher liest, obgleich es bei der Interpretation von nicht-initialisierten Referenzen sein kann, dass keine Leserechte zu den entsprechenden Speicherbereichen existieren. Hagens Lösung hingegen "schluckt" solche Fehler (durch einen try-except-Block) und gibt für diesen Fall den erwarteten Wert False zurück. In der Vergangenheit habe ich eine zur JCL ähnliche Lösung entwickelt, die mithilfe der funktion IsBadReadPtr überprüft, ob die Bereiche gelesen werden können und außerdem beim Sonderfall nil abkürzt, so dass sie als Alternative für Assigned eingesetzt werden könnte. Leider ist diese Implementierung etwas langsamer als der Ansatz der JCL und bei weitem inperformanter als die Lösung aus dem DEC. Darüber hinaus vermag auch dieser Ansatz (genauso wie die beiden anderen) nicht das volgende Szenarium korrekt zu erfassen
Delphi-Quellcode:
weil der Speicher hinter dem durch den Destructor freigegeben Objekt nicht gelöscht wird und somit weiterhin auf eine gültige Klasse verweist. Auch der MagicCode des Borland-Speichermanagers zeigt weiterhin das geforderte Kriterium, so dass ich bisher keine geeignete Lösung des Problems zur identifizierung einer gültigen Objektreferenz ausmachen konnte.
var
myObject: TObject; begin myObject:= TObject.Create; myObject.Free; if IsObject(myObject) then Hat jemand eine Lösung?
gruß, choose
|
Zitat |
Registriert seit: 17. Feb 2003 227 Beiträge |
#2
Das wird nicht leicht, in deinem unteren Beispiel ändert sich tatsächlich für den Pointer ja nichts, er zeigt immer noch auf den gleichen Speicherbereich. Der Haken an der Sache, dieser Speicher ist für die Anwendung nicht mehr freigegeben.
Genau da würde ich nach einer Lösung suchen, aber einfach ist es sicher nicht. Da muss man wohl mit Speicherverwaltung des OS rumschlagen
function getallfornothing: TGoldesel;
begin result := TGoldesel.create; end; |
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#3
Ich weiß nicht welchen Code du von IsObject() von mir analysiert hast. Meine isObject() Funktion fragt nicht ab ob der Speicherzeiger ein Magic enthält. Dies darf er auch garnicht, da es ja nicht gesagt ist das der Borland MM benutzt wird. IsObject() überprüft so wie die Borland Funktion "is" ob es sich um ein gültiges Object abgeleitet von TObject handelt. Dabei lädt sie die VMT der Objectinstance und überprüft ab da rekursiv die Klassentypen bis hinab zu TObject. Somit ist IsObject fast identisch wie IsClass(). Allerdings, habe ich eine andere Assembler Routinie als Ersatz für "is" benutzt, die eben SICHER gegen nil Pointer in den VMT's ist. Dies ist beim Borland Original "is" Operator NICHT der Fall. Zusätzlich zu dem habe ich noch einen try except Block eingebaut um eventuelle AV's abzufangen.
Aber grundsätzlich gibts für deine Problem keine saubere Lösung, das stimmt. Man kann noch eine Verbesserung durchführen indem man TObject.FreeInstance() überschreibt oder eben hookt/patcht. Die neue .FreeInstance Klassenmethode gibt so wie die originale den Speicher frei, überschreibt aber vorher mit FillChar(Self^, Self.InstanceSize) den Speicher des Objectes mit Nullen. Da Self^^ ein Zeiger auf die Klasse des Objectes ist, heist dies das nun der IS Operator auf einen NIL Zeiger zur VMT des Objectes zugreift. In diesem Moment wird also nochmal ca. 50% die Wahrscheinlichkeit erhöht falsche Objecte zu finden. Allerdings, dies hilft dir nur im Beispiel deines obigen Codes. Nach dem Object.Free ist nämlich Object^ = Object.PointerToClass == NIL. Würdest du aber sofort nach dem .Free wiederum ein Object erzeugen, mit gleicher Größe wie das vorherige, so benutzt der Speichermanager exakt den Speicher des vorher freigegeben Objectes erneut. Somit befindet sich an der Speicheradresse von Object nun wieder ein gültiges Object, es IST aber NICHT das gleiche Object !! also:
Delphi-Quellcode:
Gruß Hagen
var
A,B: TMyObject; begin A := TMyObject.Create(1); A.Free; if not IsObject(A) then "Alles Ok !" B := TMyObject.Create(2); if A = B then "sehr wahrscheinlicher Fall das A = B ist da MM Speicher wiederverwendet" if IsObject(A) then "Integer(A) = Integer(B), aber NICHT das gleiche Object" end; |
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#4
Hallo Hagen,
Zitat von negaH:
Ich weiß nicht welchen Code du von IsObject() von mir analysiert hast.
if (PInteger(PChar(AObject) - SizeOf(Integer))^ and $00000002 = $00000002) bevor der Vergleich auf die Klasse stattfindet.
Zitat von negaH:
Dabei lädt sie die VMT der Objectinstance und überprüft ab da rekursiv die Klassentypen bis hinab zu TObject.
Die von Dir beschriebene Variante mit der zusätzlichen Prüfung auf nil erfüllt mit der Möglichkeit auf die Prüfung einer konkreten Schnittstelle (Typen) noch eine zusätzliche Funktion, jedoch arbeitet sie bei zufällig (von nil verschiedenen) Werten auf zusammenhangslosen Daten fehlerhaft, so dass Du willkürliche Informationen gegen eine übergebene Klassenreferenz testest darüber hinaus nicht ausschließen kannst, in Endlosschleifen "hängenzubleiben", sofern ich das richtig verstanden habe.
Zitat von negaH:
Man kann noch eine Verbesserung durchführen indem man TObject.FreeInstance() überschreibt oder eben hookt/patcht.
Das Problem sah ich bei dieser Lösung, genau wie Du, bei der erneuten Vergabe (zB durch weiteren Thread), die auch nicht unmittelbar später erfolgen muss, sondern bei Strukturen homogener Klassen auch nach mehrfacher Freigabe und Neuvergabe von Speicher auftreten kann. Es ist die Frage, ob dies nicht vielleicht sogar gewollt ist, wenn die Routine IsObject lediglich prüfen soll, ob hinter einer Referenz tatsächlich ein Objekt steht. Es liegt in der Natur der Referenzen, dass sie nicht surjektiv sind, so dass der von Dir beschriebene Fall nicht von einer Zuweisung der Form myRef:= anotherRef; unterschieden werden kann (und sollte?). Es bleibt folglich das Problem der fälschlichen Verarbeitung von zuälligen Daten (auch mit der von mir vorgeschlagenen Prüfung auf die Selbstreferenz einer Klasse nach einer Prüfung auf die Lesbarkeit des Speichers ist dies nur zu einer endlichen Wahrscheinlichkeit möglich) und der zusätzliche Zeitaufwand bei der Freigabe von Objekten (abgesehen davon, dass nicht gerantiert ist, dass die Implementierung von TObject.FreeInstance aufgerufen wird (NodeManager)). Weiß jemand weiter?
gruß, choose
|
Zitat |
Registriert seit: 17. Feb 2003 227 Beiträge |
#5
Ich glaube euer Ansatz ist teilweise falsch, man könnte einen Speicherbereich prüfen ob es ein Objekt repräsentiert, das Ergebnis ist jedoch vollkommen uninteressant, wenn dieser Speicherbereich nicht für die Anwendung freigegeben ist. Bin nun wirklich kein Experte bzgl. Thread-Programmierung, denke aber, dass alle Threads einer Anwendung den gesamten Speicherbereich der Anwendung nutzen können. Falls dem nicht so ist, wird es noch ein wenig komplizierter.
function getallfornothing: TGoldesel;
begin result := TGoldesel.create; end; |
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#6
@Choose:
hier ist mein Code aus dem DEC -> Unit DECutil.pas
Delphi-Quellcode:
function IsClass(AObject: Pointer; AClass: TClass): Boolean; assembler; register;
asm // safer replacement for Borland's "is" operator @@1: TEST EAX,EAX JE @@3 MOV EAX,[EAX] TEST EAX,EAX JE @@3 CMP EAX,EDX JE @@2 MOV EAX,[EAX].vmtParent JMP @@1 @@2: MOV EAX,1 @@3: end; function IsObject(AObject: Pointer; AClass: TClass): Boolean; // Relacement of "is" Operator for safer access/check iff AObject is AClass begin Result := False; if AObject <> nil then try Result := IsClass(AObject, AClass); except end; end;
Zitat:
man könnte einen Speicherbereich prüfen ob es ein Objekt repräsentiert
Ein Object A wird an Addresse $1234567 erzeugt und wieder freigeben. Nun wird Object B mit gleichem Speicherverbrauch erzeugt. Der Speichermanager alloziert den Speicher auf grundf seiner internen Logik wiederum an Addresse $12345678. Somit würde ein Zugriff über eine Variable die ehemals ein Object A enthielte nun auf Object B zugreifen. Object A und B müssen aber nicht vom geleichen Typ sein oder könnten ganz andere Daten enthalten. Mit Multithreading o.ä. hat das erstmal überhaupt nichts zu tun, es geht einfach um die Aussage das es KEINE sauberen Weg gibt eine Variable die ein Object enthält das freigeben wurde zu überprüfen ob das Object noch gültig ist. Nur zwei Lösung gibt es für das Problem: 1.) sicherstellen das das freizugebene Object alle auf sich bezogenen Variablen auf NIL setzt. TComponent.FreeNotification() benutzt so eine Technik indem jedes TComponent eine TList von verlinkten Komponenten verwaltet. Speichert eine solche verlinkte Komponente eine Referenz auf Component A so kann es mit .FreeNotification() sich selbst registrieren. Beim Freigeben der Komponente ruft diese die Methode .Notification() für jeder der mit .FreeNotofiocation() registrierten Komponenten auf. Diese Technik könnte man auch für einfache Variablen benutzen. Allerdings hat diese Technik einen Hacken. Man muß nämlich jeweils BEIDE Komponenten gegenseitig registrieren. D.h. A -> B und B -> A, damit es eben nicht passieren kann das bei freigeben von A ein ungültiges B benachrichtigt wird. 2.) Einfach sicherstellen das solange irgendeine gültige Referenz existiert auch das Object nicht freigegeben wird. So arbeiten Interfaces. Ich halte diesen Weg für den saubersten da er vom Denkmodell Top-Down orientiert ist. Im dezeitigen Zustand des MM's und des Compiliers kann es keine Lösung geben. Man müsste schon den kompletten Compiler und MM umschreiben. In einem solchen Szenario wäre es vorstellbar das der Compiler über Magic einen virtuellen Pointer in eine Pointer Tabelle mappt. Diese Tabelle wäre Bestandteil des Speichermanagers und mappt die virtuellen Pointer zur korrekten Speicheradresse. D.h. jedesmal wenn der Compiler ein Object derefernzieren will muß der Compiler diese Magic benutzen. Nun ist es möglich die Zeigertabelle zum mappen so aufzubauen das ein sequentieller Gültigkeitszähler integriert wird. Gruß Hagen |
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#7
Hallo Hagen,
zunächst einmal möchte ich die Problematik mit der Endlosschleife (zugegen, recht unwahrscheinlich) bei Deiner Implementierung auf ungüültigen Daten genauer demonstrieren:
Delphi-Quellcode:
um Dir weiterhin beizupflichten: Es können auch Objekte anderer Klassen an derselben Adresse abgelegt werden. Weil die Identität eines Objekts nach dem Ansatz in Delphi aber genau durch diesen Ort definiert ist und Referenzen weiterhin nicht eineindeitig (sondern nur injektiv) einem Objekt zugeordnet sind, ist es fraglich, ob die Funktion IsObject nicht vielleicht per definition korrekt arbeitet, wenn sie für diesen Fall erneut True zurückgibt.
procedure TakeIsClassForARide;
var myCircularData : record vmtParent : Pointer; // -36 vmtSafeCallException : Integer; // -32 vmtAfterConstruction : Integer; // -28 vmtBeforeDestruction : Integer; // -24 vmtDispatch : Integer; // -20 vmtDefaultHandler : Integer; // -16 vmtNewInstance : Integer; // -12 vmtFreeInstance : Integer; // -8 vmtDestroy : Integer; // -4 ClassOffsetZero : Pointer; end; begin myCircularData.vmtParent:= @myCircularData.ClassOffsetZero; myCircularData.ClassOffsetZero:= @myCircularData.ClassOffsetZero; IsClass(@myCircularData, TObject); end; Die Idee mit den Threads wollte ich nur anführen, um zu zeigen dass auch bei Konstrukten der Art
Delphi-Quellcode:
var
myRef: TObject; begin myRef:= TObject.Create; myRef.Free; // thread may create another object o right now IsObject(myObject) // <- result is undefined (myObject may be object o)
Zitat von negaH:
1.) sicherstellen das das freizugebene Object alle auf sich bezogenen Variablen auf NIL setzt.
Zitat von negaH:
2.) Einfach sicherstellen das solange irgendeine gültige Referenz existiert auch das Object nicht freigegeben wird. So arbeiten Interfaces. Ich halte diesen Weg für den saubersten da er vom Denkmodell Top-Down orientiert ist.
Zitat von negaH:
Im dezeitigen Zustand des MM's und des Compiliers kann es keine Lösung geben.
Anbei der Code, den ich verwende
Delphi-Quellcode:
Es bleibt nach meiner Ansicht die Frage nach einer eine möglichst sicheren Prüfung auf zufälligen Daten.
function IsClass(const AClass: TClass): Boolean; assembler;
asm // EAX = AClass OR EAX, EAX // AClass = nil JE @OUT MOV EBX, EAX // store class reference PUSH 4 // valid pointer -> class self reference readable ADD EAX, vmtSelfPtr PUSH EAX CALL IsBadReadPtr OR EAX, EAX JNE @FALSE MOV EAX, EBX SUB EAX, EAX.vmtSelfPtr // test class against self reference JNE @FALSE INC EAX // Result:= True RET @FALSE: XOR EAX, EAX // Result:= False @OUT: end; function IsObject(const AnObject: TObject): Boolean; assembler; asm // EAX = AnObject OR EAX, EAX // AnObject = nil JE @OUT MOV EBX, EAX // store object reference PUSH 4 // valid Pointer -> class readable PUSH EAX CALL IsBadReadPtr OR EAX, EAX JNE @FALSE MOV EAX, [EBX] // class reference JMP IsClass // Result:= IsClass(EAX) @FALSE: XOR EAX, EAX // result:= False @OUT: end;
gruß, choose
|
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#8
Hi Choose,
deine Überprüfung auf zirkuläre Referenzen ist ebenfalls nicht 100%'tig sicher. Angenommen die Klasse ist die 10'te Klasse in einer Hirarchie. Dann würde deine Absicherung im Source nur mit 1/(10*10)'tel besser sein als garkeine Überprüfung auf zirkuläre Referenzen. Denn es kann an jeder Stelle der VTM.Parent Hierarchie die zirkulare Referenz zu jeder der anderen Klassen in der Hirarchie auftreten. Somit müsste deine IsClass() Überprüfung auf zirkuläre Referenzen viel weiter ausgebaut werden. Es gibt noch drei zusätzliche Möglichkeiten IsClass() mehr abzusichern: 1.) in der RTTI/VMT ist selber ein Feld das als vmtSelfPtr bezeichnet wird. Eine gültige Klasse muß in diesem Feld auf sich selber zeigen. 2.) eine gültige Klasse sollte immer im Codesegment gespeichert werden. Bis auf einem Ausnahme, meine eigene Testimplementierung, kenne ich keinen Delphi Source der dynamisch Klassen zur Laufzeit konstruiert. D.h. man kann mit 99.99% Wahrscheinlichkeit sagen das der Zeiger auf die Klasse im Codesegement liegen muß. 3.) man kann über alle Typ Information der Anwendung und seiner geladenen Module iterieren. Die Klassen Strukturen sind nur ein Teil dieser Typinformation die der Compiler hardcoded erzeugt. Man kann nun diese Iteration über die Typinfo's als Referenzquelle für gültige Zeiger von allen im Projekt verwendeten Klassen benutzen. Damit ließe sich auch das Problem eventueller zirkulärer Referenzen beseitigen. Man iteriert über die Typinformation bis man die Klasse zum Object gefunden hat. Nun geht man iterativ die Klassenhierarchie top-down. Da fällt mir ein, zirkuläre Referenzen IN der Klassenhierarchie können nur auftreten wenn man das Codesegment modifiziert, also patcht. Sie sind also enorm unwahrscheinlich und würden NICHT eine Object-Instance ansich betreffen sondern die komplette Klasse. D.h. ALLE Objectinstancen der gleichen Klasse würden den selben Fehler in ihrer Klassenhierarchie enthalten. Ziemlich unwahscheinlich also da der Compilier die Klassenhierarchien hardcoded im Codesegment eincompiliert. Auf Grund der Delphi Sprachdefinitionen ist es aber nicht möglich zirkuläre Klassenhierarchien zu konstruieren. Es müsste also schon im Codesegment hineingepatcht wurden sein. Gruß Hagen |
Zitat |
Registriert seit: 2. Nov 2003 Ort: Bei Kiel, SH 729 Beiträge Delphi 2006 Architect |
#9
Hallo Hagen,
Zitat von negaH:
deine Überprüfung auf zirkuläre Referenzen ist ebenfalls nicht 100%'tig sicher.
Auch mein Vorschlag könnte zufällige Daten missinterpretieren (also True zurückgeben obgleich hinter einem Datenbereich kein Objekt existiert), aber auch im worst-case nicht zu einer Endlosschleife führen. Hast Du zu solchen Daten eine bessere Idee? Darüber hinaus sprach ich zirkuläre Referenzen im Zusammenhang mit Interfaces an, konkret: die von Delphi implementierten Referenzzählung. Auch hier ging es weder um die Vererbungshierarchie (auf die bei Interfaces wohl auch eher selten geprüft wird, obgleich dies möglich ist) noch auf eventuelle Zyklen in ihr sondern um das Problem von "verhakten Clustern" von Objekten, die gegenseitig ihre Referenzen halten und in anderen Umegbungen nur mit einem Garbage Collector aufgelöst werden konnten.
Zitat von negaH:
1.) in der RTTI/VMT ist selber ein Feld das als vmtSelfPtr bezeichnet wird. Eine gültige Klasse muß in diesem Feld auf sich selber zeigen.
Zitat von negaH:
2.) eine gültige Klasse sollte immer im Codesegment gespeichert werden. Bis auf einem Ausnahme, meine eigene Testimplementierung, kenne ich keinen Delphi Source der dynamisch Klassen zur Laufzeit konstruiert.
Zitat von negaH:
3.) man kann über alle Typ Information der Anwendung und seiner geladenen Module iterieren.
Die anderen Fälle sollten bei den bisherigen Betrachtungen als ausgeschlossen gelten. Für diesen Ansatz müsste jedoch sichergestellt werden, dass die TypeInfo/TypeData jeder Klasse zugänglich ist. Bisher habe ich Registraturen (zB für ein dynamisches Factory-Pattern) ähnlich der Funktion RegisterClass händisch implementiert. Kannst Du einmal zeigen, wie man über alle RTTI aller Datentypen iterieren kann?
gruß, choose
|
Zitat |
Registriert seit: 25. Jun 2003 Ort: Thüringen 2.950 Beiträge |
#10
Hi Choose,
ich hatte hier im Forum schon mal einen solchen Code gepostet. Dieser Source konnte über alle deklarierten TypInfos der geladenenen Module iterieren. D.h. alle TypInfos die auch mit der Funktion TypInfo(XYZ) abgefragt werden können konnten mit der Funktion EnumTypeInfo() durchiteriert werden. Nun, ich habe danch im Forum gesucht und konnte es nicht mehr finden, vielleicht findest du es ja. Gruß Hagen |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |