2013-09-04 13 views
23

私は正しいと思われるDelphiコードに対して予期しないアクセス違反がありますが、誤ってコンパイルされているようです。私はそれを減らすことができます匿名プロシージャとネストされたプロシージャを組み合わせたときのコードが間違っています

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; 

私には、S := 'Hello, world!'が間違った場所に格納されています。そのため、アクセス違反が発生するか、ShowMessage(S)が "Hello"を示します(また、匿名プロシージャの実装に使用されるオブジェクトを解放するときにアクセス違反が発生することもあります)。

私はすべてのアップデートがインストールされているDelphi XEを使用しています。

これがどこに問題を引き起こすかをどのように知ることができますか?私は匿名の手続きを避けるためにコードを書き直す方法を知っていますが、間違ったコードにつながる状況を正確に把握することができないので、どこを避けるべきかわかりません。

これがDelphiのそれ以降のバージョンで修正されているのは興味深いですが、興味深いだけではありません。この時点でアップグレードはオプションではありません。

QCの最新のレポートでは、同様の#91876が見つかりましたが、それはDelphi XEで解決されています。

更新:AlexSCのコメントに基づいて

、若干の変更で:

... 

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

... 

が作業を行います。正しいバージョンが失敗したプログラムで生成されたコードを見ていない

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 

ある一方

失敗プログラムで

S := 'Hello, world!'; 

用に生成されたマシンコードは

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

ありますSがコンパイラ生成クラスに移動されましたはです。ネストされたメソッドの外側ローカル変数へのアクセス方法ローカル変数のアクセス方法

+0

私のテストでは、「[DCC警告] Unit1.pas(45):W1036変数 '$ frame'が初期化されていない可能性があります。私は$フレーム変数を宣言していないので、匿名メソッドを実装するインターフェイスを宣言するときに、コンパイラによって生成されたと仮定します。この警告は、コンパイラによってすべてが正しく行われたわけではないことを示しているため、バグのようです。文字列としてS変数を宣言するようにコードを変更すると、問題が早期に表示されます。デバッグは、S変数が生成されたコードによって適切に処理されなかったことを示唆しています。 – AlexSC

+0

@AlexSC "初期化されていない可能性があります"という検出は悪いことですが、実際の問題を指摘せず、生成されたコードには影響しない誤検出が数多くあるため、無視しても安全です。また、正しく動作するより単純なコードで警告( '$ frame'コンパイラ生成変数を含む)を得ることもできます。 – hvd

+1

XE2でコンパイルしてもうまく動作します –

答えて

0

これはどこで問題が発生するのでしょうか。

この時点では分かりにくいです。
Delphi XE2での修正の性質を知っていれば、より良い立場にいます。
匿名機能の使用を控えることができます。
デルファイには手続き型の変数があります。そのため、匿名関数を準備する必要はありません。
http://www.deltics.co.nz/blog/posts/48を参照してください。

これはXE2で修正されてい@Sertac Akyuzによると

デルファイのそれ以降のバージョンで固定されているかどうかを知ることが私には興味深いものになるだろう。

個人的に私は匿名メソッドを嫌い、私たちのコードベースのかなりの部分が匿名(イベントハンドラ)になっているため、Javaプロジェクトでそれらを使用することを禁止する必要がありました。
極端な節度で使用すると、私はユースケースを見ることができます。
しかし、私たちはプロシージャ変数と入れ子になったプロシージャを持っているデルファイで...それほど多くはありません。

+0

申し訳ありませんが、これは私の質問に答えるために何もしません。 "それは明らかに分かりません。"本当に?ネストされた関数と匿名関数の特定の組み合わせで発生するバグです。私よりもスキルや知識が豊富な人は、私が求めている「特定の組み合わせ」をより詳細に記述することができます。それらを完全に避ける必要はありません。 – hvd

+1

私は、コードを複雑にすることなく回避するのが難しいユースケースを1つ持っています。これは、「手続きへの参照」を返す関数で、渡され、他のコードによって呼び出されることになります。コンパイラによって生成されたヘルパーオブジェクトは、 'procedure to reference'型が参照カウントされるため、自動的に解放されます。 'procedure of object'では、オブジェクトが解放されることを保証するために、たくさんの特別なコードを手動で追加する必要があります。唯一の実用的な代替策は、手動で 'interface'を定義し、それをヘルパークラスに実装させることです。 Javaにはガベージコレクションがあるので、問題はありません。 – hvd

1

あなたが投稿したスニペットを仮定しているだけで、全体のアセンブラコードを見ることなく(おそらく、失敗したスニペットでポインタが移動されたのは正しいバージョンでは、 。

S:= SまたはS:= ''は、コンパイラに自身の参照を作成させ、いくつかのメモリを割り当てることもできます。

また、S:= SまたはS:= ''なしでアクセス違反が発生する理由は、(文字列に割り当てられたメモリがない場合はS:PCharと宣言しただけなので)アクセス違反が発生する割り当てられていないメモリがアクセスされたためです。

単純にS:Stringを宣言すると、これは起こりません。コメント拡張後

追加:

A PChar型が存在しなければならないデータ構造へのポインタのみです。 PCharの別の共通の問題は、ローカル変数を宣言し、その変数に他のProcsにPCharを渡すことです。なぜなら、ルーチンが終了するとローカル変数は解放されますが、PCharはまだそれを指し示します一度アクセスされた違反にアクセスする。

ドキュメントごとに存在する唯一の可能性は、const S: PChar = 'Hello, world!'のように宣言しています。これは、コンパイラが相対アドレスを解決できるためです。しかし、これは上の例のような定数ではなく、変数でのみ機能します。上記の例のようにするには、PCharが指す文字列リテラルに割り当てられるストレージがS:String; P:PChar; S:='Hello, world!'; P:=PChar(S);またはそれに類するものである必要があります。

文字列または整数の宣言に失敗した場合、おそらく変数がどこかに消えたり、procで突然表示されなくなる可能性がありますが、これは既に説明した既存のPCharの問題とは関係のない別の問題です。

最終的な結論:

それはS:PChar; S:='Hello, world!'を行うことは可能ですが、コンパイラは、単に実行可能ファイルに保存されているんconst S: PChar = 'Hello, world!'などのローカルまたはグローバル定数、S := 'Hello'第二は、その後もに保存されている別のものを作成して、それを割り当てExecutableなど - しかし、Sは、割り当てられた最後のものだけを指し示します。Sは、割り当てられた最後のものを指しているため、正確な位置を知らなくても、実行可能ファイルに残りますが、

最後にSのいずれかに応じて、Hello, world!またはHelloのいずれかになります。上の例では、私は最後のものしか推測できませんでした。おそらく、コンパイラは推測しかできないでしょう。また、最適化やその他の予測不可能な要因によっては、Sが、最後のものではなく割り当てられていないMemを突然指すことがあります。Showmessage(S)アクセス違反が発生します。

+0

私はあなたに保証します、私はポインタの仕組みを知っています。ポインター変数にはポイント先のデータが含まれていないことがわかります。しかし、文字列リテラルは参照カウントされず、実行可能イメージに直接存在し、コピーする必要のあるデータはなく、ポインタ自体です。 'string 'を使わなかったのは、' PChar'のような管理されていない型は、より簡単なアセンブリを生成し、生成されたアセンブリの検査を容易にするためです。しかし、 'string'も使用すると失敗します。実際、' Integer'型の変数を使用しても問題は見えます。 – hvd

+0

おそらく失敗するかもしれませんが、あなたはまだ「Hello、world!」を割り当てることはできません。それはそれが唯一のデータへのポインタだからですが、上の例ではそれが指し示すデータはありません。 'String'や' Integer'を宣言してもまだ失敗した場合、おそらく変数はどこかに消えてしまいます。あるいは、procのためにもう突然表示されません。 Proc全体の完全なASM Sourceは、実際に何が起こったのかを明らかにするのに役立ち、変数が消える場所やアクセス違反がどこで発生するのかを監視します。 – Amenominakanushi

+0

私がすでに言ったように、文字列リテラルは参照されません、彼らは遠ざかりません。心配することなく文字列リテラルへのポインタを保持することができます。それは実装の詳細だけではなく、信頼できるドキュメントで明示的に約束されています。 [ここで読む](http://docwiki.embarcadero.com/RADStudio/XE/en/Internal_Data_Formats#Long_String_Types)生成されたアセンブリを検査するために、私はすでにそれを行い、その問題の関連する詳細を示しました。変数は消えず、アクセス違反がどこで発生しているかがわかりました。 – hvd

関連する問題