double? = double? + double?
質問
私は StackOverflow コミュニティに、この単純な C# コードで私がおかしくなっているかどうかを確認したいと思いました。
私は Windows 7 で開発しており、.NET 4.0, x64 Debug でこれを構築しています。
私は以下のコードを持っています。
static void Main()
{
double? y = 1D;
double? z = 2D;
double? x;
x = y + z;
}
デバッグして最後の中括弧にブレークポイントを置くと、ウォッチウィンドウとイミディエイトウィンドウにx = 3が表示されるはずですが、代わりにx = nullが表示されます。
x86 でデバッグすると、正常に動作するようです。 x64 コンパイラーに何か問題があるのでしょうか、それとも私に何か問題があるのでしょうか?
どのように解決するのですか?
Douglas の回答は、JIT がデッドコードを最適化することについては正しいです (
両方とも
x86 と x64 のコンパイラーはこれを行います)。 しかし、もし JIT コンパイラがデッド コードを最適化しているとしたら、それはすぐにわかることです。
x
はLocalsウィンドウに表示されないからです。 さらに、ウォッチ&イミディエイトウィンドウでは、それにアクセスしようとすると、代わりに次のようなエラーが表示されます: "The name 'x' does not exist in the current context"(名前 'x' は現在のコンテキストには存在しません)。 これは、あなたが説明したようなことが起こっているのではありません。
あなたが見ているものは、実際には Visual Studio 2010 のバグです。
まず、私のメインマシンでこの問題の再現を試みました。Win7x64 と VS2012 です。 .NET 4.0 ターゲットの場合。
x
は、閉じ中括弧で壊れると3.0Dと同じになります。 .NET 3.5ターゲットも試してみることにして、それで。
x
も null ではなく 3.0D に設定されました。
.NET 4.0 の上に .NET 4.5 をインストールしているので、この問題の完全な再現はできないので、仮想マシンをスピンアップして、その上に VS2010 をインストールしました。
ここで、問題を再現することができました。 ブレークポイントを
Main
メソッドの閉じる中括弧にブレークポイントを設定すると、ウォッチ ウィンドウとローカル ウィンドウの両方で、次のようになりました。
x
は
null
. ここからが面白いところです。 代わりにv2.0ランタイムをターゲットにしたところ、そこでもnullであることがわかりました。 私の別のコンピューターには、同じバージョンの .NET 2.0 ランタイムがあり、正常に
x
の値で
3.0D
.
では、何が起こっているのでしょうか? windbgでいろいろ調べてみたところ、問題が見つかりました。
VS2010 は、実際に代入される前に x の値を表示しています。 .
を過ぎているので、見た目と違うのはわかりますが、命令ポインタは
x = y + z
の行を越えているからです。 メソッドに数行のコードを追加することで、自分でテストすることができます。
double? y = 1D;
double? z = 2D;
double? x;
x = y + z;
Console.WriteLine(); // Don't reference x here, still leave it as dead code
最後の中括弧にブレークポイントを設定すると、ローカルおよびウォッチウィンドウに
x
と同じで
3.0D
. しかし、コードを順を追って見ていくと、VS2010では
x
が割り当てられるまで
の後に
を通過した時点で
Console.WriteLine()
.
このバグが Microsoft Connect に報告されたことがあるかどうかは知りませんが、このコードを例にして報告したほうがいいかもしれません。 しかし、これは明らかに VS2012 で修正されたので、これを修正するアップデートがあるかどうかはわかりません。
以下は、JITとVS2010で実際に起こっていることです。
元のコードで、VS が何をしているのか、なぜそれが間違っているのかがわかります。 また
x
変数が最適化されていないこともわかります (最適化を有効にしてコンパイルするようにアセンブリをマークしていない限り)。
まず、IL のローカル変数の定義を見てみましょう。
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<float64> y,
[1] valuetype [mscorlib]System.Nullable`1<float64> z,
[2] valuetype [mscorlib]System.Nullable`1<float64> x,
[3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0000,
[4] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001,
[5] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0002)
これは、デバッグモードでの通常の出力です。 Visual Studio は代入中に使用するローカル変数を重複して定義し、CS* 変数からそれぞれのユーザー定義ローカル変数にコピーするために余分な IL コマンドを追加しています。 以下は、この現象を示す対応する IL コードです。
// For the line x = y + z
L_0045: ldloca.s CS$0$0000 // earlier, y was stloc.3 (CS$0$0000)
L_0047: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_004c: conv.r8 // Convert to a double
L_004d: ldloca.s CS$0$0001 // earlier, z was stloc.s CS$0$0001
L_004f: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0054: conv.r8 // Convert to a double
L_0055: add // Add them together
L_0056: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0) // Create a new nulable
L_005b: nop // NOPs are placed in for debugging purposes
L_005c: stloc.2 // Save the newly created nullable into `x`
L_005d: ret
WinDbgでより深いデバッグをしましょう。
VS2010 でアプリケーションをデバッグし、メソッドの終わりにブレークポイントを残しておけば、非侵襲モードで簡単に WinDbg をアタッチすることができます。
のフレームは以下の通りです。
Main
メソッドをコールスタックに格納しています。 IP(命令ポインタ)を気にしています。
0:009> !clrstack OSのスレッドID 0x135c (9) 子SPのIPコールサイト 000000001c48dc00 000007ff0017338d ConsoleApplication1.Program.Main(System.String[]) [といった具合に...〕。]
のネイティブマシンコードを表示すると
Main
メソッドのネイティブのマシンコードを表示すると、VS が実行を中断した時点でどのような命令が実行されていたかを確認できます。
000007ff`00173388 e813fe25f2 call mscorlib_ni+0xd431a0 (000007fe`f23d31a0) (System.Nullable`1[[System.Double, mscorlib]]..ctor(Double), mdToken: 0000000006001ef2) ****000007ff`0017338d cc int 3****. 000007ff`0017338e 8d8c2490000000 lea ecx,[rsp+90h]. 000007ff`00173395 488b01 mov rax,qword ptr [rcx]. 000007ff`00173398 4889842480000000 mov qword ptr [rsp+80h],rax 000007ff`001733a0 488b4108 mov rax,qword ptr [rcx+8]. 000007ff`001733a4 4889842488000000 mov qword ptr [rsp+88h],rax 000007ff`001733ac 488d8c2480000000 lea rcx,[rsp+80h]. 000007ff`001733b4 488b01 mov rax,qword ptr [rcx] (RSP+80h)。 000007ff`001733b7 4889442440 mov qword ptr [rsp+40h],rax 000007ff`001733bc 488b4108 mov rax,qword ptr [rcx+8]. 000007ff`001733c0 4889442448 mov qword ptr [rsp+48h],rax 000007ff`001733c5 eb00 jmp 000007ff`001733c7 000007ff`001733c7 0f28b424c0000000 movaps xmm6,xmmword ptr [rsp+0C0h]. 000007ff`001733cf 4881c4d8000000 add rsp,0D8h 000007ff`001733d6 C3 ret
から取得した現在のIPを使用して
!clrstack
で
Main
で実行が中断されたことがわかります。
の直後
への呼び出しで実行が中断されたことがわかります。
System.Nullable<double>
のコンストラクタの呼び出しの直後です。 (
int 3
はデバッガが実行を停止するために使用する割り込みです) その行を*で囲みましたが、さらにその行をマッチングして
L_0056
に一致させることができます。
続く x64 アセンブリでは、実際にローカル変数に代入される
x
. 命令ポインターはまだそのコードを実行していないため、VS2010 は
x
変数がネイティブコードによって代入される前に、VS2010 は早期にブレークします。
EDIT: x64 では
int 3
命令は、上で見たように、代入コードの前に置かれます。 x86では、その命令は代入コードの後に配置されます。 そのため、x64でだけVSが早く壊れるのはこのためです。 これがVisual Studioのせいなのか、JITコンパイラのせいなのか、判断に迷うところです。 どちらのアプリケーションがブレークポイントフックを挿入しているかは不明です。
関連
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み] 保護レベルによりアクセス不能になりました。
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み】Entity FrameworkからのSqlException - セッション内で他のスレッドが動作しているため、新しいトランザクションは許可されません。
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み】値をNULLにすることはできません。パラメータ名:source
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み] .NETでのdecimal, float, doubleの違い?
-
[解決済み] C#でDecimalをDoubleに変換する方法は?
-
[解決済み] Visual Studio build fails: unable to copy exe-file from obj 001debug to bin 001debug
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み】指定されたキャストが有効でない?
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み] UnityでOnCollisionEnterが呼ばれない
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー