2013-09-04 15 views
23

Ich habe einige unerwartete Zugriffsverletzungen für Delphi-Code, die ich für richtig halte, aber scheint falsch zu kompilieren. Ich kann esFalscher Code bei der Kombination von anonymen und verschachtelten Prozeduren

reduzieren
procedure Run(Proc: TProc); 
begin 
    Proc; 
end; 

procedure Test; 
begin 
    Run(
    procedure 
    var 
     S: PChar; 

     procedure Nested; 
     begin 
     Run(
      procedure 
      begin 
      end); 
     S := 'Hello, world!'; 
     end; 

    begin 
     Run(
     procedure 
     begin 
      S := 'Hello'; 
     end); 
     Nested; 
     ShowMessage(S); 
    end); 
end; 

Was für mich passiert ist, dass S := 'Hello, world!' an der falschen Stelle speichert. Aus diesem Grund wird entweder eine Zugriffsverletzung ausgelöst, oder ShowMessage(S) zeigt "Hello" (und manchmal wird eine Zugriffsverletzung ausgelöst, wenn die Objekte freigegeben werden, die zum Implementieren anonymer Prozeduren verwendet werden).

Ich verwende Delphi XE, alle Updates installiert.

Woher weiß ich, wo dies zu Problemen führt? Ich weiß, wie ich meinen Code umschreiben kann, um anonyme Prozeduren zu vermeiden, aber ich habe Probleme herauszufinden, in welchen Situationen sie zu falschem Code führen, also weiß ich nicht, wo ich sie vermeiden soll.

Es wäre interessant zu wissen, ob dies in späteren Versionen von Delphi behoben ist, aber nichts mehr als interessant, Upgrade ist keine Option zu diesem Zeitpunkt.

Auf QC, den neuesten Bericht kann ich die ähnliche #91876 finden, aber das ist in Delphi XE gelöst.

aktualisieren:

Basierend auf AlexSC Kommentare, mit einer geringfügigen Änderung:

... 

     procedure Nested; 
     begin 
     Run(
      procedure 
      begin 
      S := S; 
      end); 
     S := 'Hello, world!'; 
     end; 

... 

funktioniert.

Der erzeugte Maschinencode für

S := 'Hello, world!'; 

im andernfalls Programm ist

ScratchForm.pas.44: S := 'Hello, world!'; 
004BD971 B89CD94B00  mov eax,$004bd99c 
004BD976 894524   mov [ebp+$24],eax 

während die richtige Version

ScratchForm.pas.45: S := 'Hello, world!'; 
004BD981 B8B0D94B00  mov eax,$004bd9b0 
004BD986 8B5508   mov edx,[ebp+$08] 
004BD989 8B52FC   mov edx,[edx-$04] 
004BD98C 89420C   mov [edx+$0c],eax 

Der generierte Code in das fehlerhafte Programm ist nicht zu sehen, dass S in eine vom Compiler generierte Klasseverschoben wurdeist wie auf externe lokale Variablen verschachtelter Methoden zugegriffen wird wie auf lokale Variablen zugegriffen wird.

+0

In meinen Tests bekam ich diese Warnung "[DCC Warnung] Unit1.pas (45): W1036 Variable '$ Rahmen' wurde möglicherweise nicht initialisiert". Da ich keine $ -Frame-Variable deklariert habe, gehe ich davon aus, dass sie vom Compiler generiert wurde, als die Interfaces deklariert wurden, die die anonymen Methoden implementieren. Die Warnung deutet darauf hin, dass nicht alles vom Compiler korrekt ausgeführt wurde, es scheint also ein Fehler zu sein. Wenn Sie den Code so ändern, dass die S-Variable deklariert wird, wird das Problem früher angezeigt. Das Debugging legt nahe, dass die S-Variable vom generierten Code nicht korrekt behandelt wurde. – AlexSC

+0

@AlexSC Die Erkennung "möglicherweise noch nicht initialisiert" ist notorisch schlecht, es gibt Unmengen von Fehlalarmen, die nicht auf ein echtes Problem hinweisen und keinen generierten Code beeinflussen. Daher sollte diese Warnung ignoriert werden. Ich kann diese Warnung (einschließlich der '$ frame'-Compiler-generierten Variable) auch in einfacherem Code erhalten, der richtig funktioniert. – hvd

+1

Kompiliert und funktioniert ok in XE2 –

Antwort

0

Woher weiß ich, wo dies zu Problemen führt?

Es ist zu diesem Zeitpunkt schwer zu sagen.
Wenn wir die Art der Fehlerbehebung in Delphi XE2 wüssten, wären wir in einer besseren Position.
Sie können nur anonyme Funktionen verwenden.
Delphi hat prozedurale Variablen, daher ist die Notwendigkeit für anonyme Funktionen nicht so schlimm.
Siehe http://www.deltics.co.nz/blog/posts/48.

Es wäre für mich interessant zu wissen, ob diese in späteren Versionen von Delphi fixiert ist

Nach @Sertac Akyuz dies in XE2 behoben wurde.

Persönlich mag ich keine anonymen Methoden und musste Leute davon abhalten, sie in meinen Java-Projekten zu verwenden, weil ein großer Teil unserer Code-Basis anonym wurde (Event-Handler).
Bei extremer Moderation kann ich den Anwendungsfall sehen.
Aber in Delphi, wo wir prozedurale Variablen und verschachtelte Verfahren haben ... Nicht so sehr.

+0

Sorry, aber das beantwortet meine Frage nicht. "Das ist offensichtlich unerkennbar." "Ja wirklich?" Es ist ein Fehler, der bei bestimmten Kombinationen von verschachtelten Funktionen und anonymen Funktionen auftritt, und jemand mit mehr Fähigkeiten oder Kenntnissen als ich wäre sicherlich in der Lage, diese "bestimmten Kombinationen" ausführlicher zu beschreiben, was ich gerade frage. Es gibt keine Notwendigkeit, sie vollständig zu vermeiden. – hvd

+1

Ich habe einen Anwendungsfall, in dem es schwierig ist, zu vermeiden, ohne den Code zu verkomplizieren: eine Funktion, die eine 'Referenz auf Prozedur 'zurückgibt, die herumgereicht wird und von anderem Code aufgerufen wird. Das vom Compiler generierte Hilfsobjekt wird automatisch freigegeben, da der Typ "Verweis auf Prozedur" als Referenz gezählt wird. Mit 'procedure of object' würde ich manuell viel zusätzlichen Code hinzufügen müssen, um sicherzustellen, dass das Objekt freigegeben wird. Die einzige praktische Alternative wäre, manuell eine 'Schnittstelle' zu ​​definieren und sie von der Hilfsklasse implementieren zu lassen. Java hat eine Garbage-Collection, also ist es dort kein Problem. – hvd

1

Ohne den ganzen Assembler-Code für das Ganze zu sehen (Prozedur Test) und nur auf dem von Ihnen geposteten Snippet angenommen, ist es wahrscheinlich, dass auf dem fehlerhaften Snippet nur ein Pointer verschoben wurde, wo auf der richtigen Version auch einige Daten verschoben sind .

So scheint es, dass S: = S oder S: = '' bewirkt, dass der Compiler eine eigene Referenz erstellt und sogar etwas Memory zuweisen könnte, was erklären würde, warum es dann funktioniert.

Ich gehe auch davon aus, dass eine Zugriffsverletzung ohne S: = S oder S: = 'auftritt, weil, wenn kein Speicher für die Zeichenfolge reserviert ist (erinnern Sie sich nur S: PChar deklariert), eine Zugriffsverletzung ausgelöst wird weil auf nicht zugewiesenen Speicher zugegriffen wurde.

Wenn Sie einfach S: String deklarieren, wird dies wahrscheinlich nicht passieren.

Zugänge nach längerer Kommentar:

A PChar auf die Datenstruktur nur ein Zeiger ist, existieren, müssen. Ein weiteres häufiges Problem bei PChar ist es, lokale Variablen zu deklarieren und dann einen PChar an diese Variable zu anderen Procs zu übergeben, denn die lokale Variable wird freigegeben, sobald die Routine beendet ist, aber der PChar wird immer noch darauf zeigen, die dann erhöht Zugriffsverletzungen, auf die einmal zugegriffen wurde.

Die einzige Möglichkeit, die per Dokumentation existiert, ist, so etwas zu deklarieren const S: PChar = 'Hello, world!' dies funktioniert, weil der Compiler eine relative Adresse zu ihm auflösen kann. Aber das funktioniert nur für Konstanten und nicht für Variablen wie im obigen Beispiel. Wenn Sie wie im obigen Beispiel vorgehen, muss Storage für das String-Literal reserviert werden, auf das der PChar dann auf S:String; P:PChar; S:='Hello, world!'; P:=PChar(S); oder ähnlich verweist.

Wenn es noch immer mit der Deklaration von String oder Integer fehlschlägt, verschwindet die Variable irgendwo irgendwo oder plötzlich nicht mehr in einem Proc, aber das wäre ein anderes Problem, das nichts mit dem bereits existierenden PChar-Problem zu tun hat.

Schluss Fazit:

Es ist möglich, S:PChar; S:='Hello, world!' zu tun, aber der Compiler dann einfach ordnet sie als lokale oder globale Konstante wie const S: PChar = 'Hello, world!' hat, die in Executable gespeichert wird, erstellt eine zweite S := 'Hello' dann eine andere, die auch in gespeichert Executable und so weiter - aber S zeigt dann nur auf den letzten zugewiesenen, alle anderen sind noch in der ausführbaren Datei, aber nicht mehr zugänglich, ohne den genauen Standort zu kennen, weil S nur auf den letzten zugewiesenen Punkt zeigt.

Also abhängig davon war die letzte S Punkte entweder Hello, world! oder Hello.Auf dem obigen Beispiel kann ich nur raten, welcher der letzte war und wer weiß, vielleicht kann der Compiler auch nur raten und je nach Optimierungen und anderen unvorhersehbaren Faktoren S könnte plötzlich auf nicht zugeordnete Mem statt auf die letzte durch die Zeit was ausgeführt wird Dann wird eine Zugriffsverletzung ausgelöst.

+0

Ich versichere Ihnen, ich weiß, wie Zeiger funktionieren. Ich weiß, dass Pointer-Variablen die angegebenen Daten nicht enthalten. String-Literale werden jedoch nicht refcounted, sie existieren direkt in dem ausführbaren Bild und es gibt keine Daten, die kopiert werden müssen, nur der Zeiger selbst. Ich habe 'string' nicht verwendet, da nicht verwaltete Typen wie' PChar' einfachere Assemblierung erzeugen und die generierte Assembly einfacher zu überprüfen sind. Aber es schlägt auch fehl, wenn Sie 'string' verwenden, und tatsächlich können Sie das Problem sehen, selbst wenn Sie eine Variable vom Typ' Integer' verwenden. – hvd

+0

Vielleicht scheitert es auch, aber du kannst immer noch nicht einfach 'Hallo, Welt!' B. zu einem PChar, weil es nur ein Zeiger auf Daten ist, aber auf dem obigen Beispiel gibt es keine Daten, auf die es zeigt. Wenn es immernoch fehlschlägt, "String" oder "Integer" zu deklarieren, dann verschwindet die Variable irgendwo entlang oder ist plötzlich für einen Proc nicht mehr sichtbar. Eine vollständige ASM-Quelle des gesamten Proc würde helfen, herauszufinden, was wirklich passiert, sonst trete durch und beobachte, wo die Variable verschwindet oder wo genau die Zugriffsverletzung auftritt, um sie einzugrenzen. – Amenominakanushi

+0

Wie ich bereits sagte, String-Literale werden nicht gezählt, sie gehen nicht weg. Sie können einen Zeiger auf ein Zeichenfolgenliteral ohne Sorgen behalten. Das ist nicht nur ein Implementierungsdetail, sondern ein ausdrückliches Versprechen in der Dokumentation, auf das man sich verlassen kann. [Lies es hier.] (Http://docwiki.embarcadero.com/RADStudio/XE/en/Internal_Data_Formats#Long_String_Types) Wie zur Überprüfung der generierten Assembly habe ich das bereits getan und die relevanten Details in der Frage gezeigt. Die Variable verschwindet nicht und ich habe bereits genau herausgefunden, wo die Zugriffsverletzung auftritt. – hvd