[過去ログ] C#, C♯, C#相談室 Part96 (1002レス)
上下前次1-新
抽出解除 必死チェッカー(本家) (べ) 自ID レス栞 あぼーん
このスレッドは過去ログ倉庫に格納されています。
次スレ検索 歴削→次スレ 栞削→次スレ 過去ログメニュー
137(2): デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 17:49:18.74 ID:uT8cdwkS0(1/4) AAS
相談させてください。
IntPtr を ref int に変換するために以下のようなコードを書くと、期待通り False と表示されます。
IntPtr ptr = Marshal.AllocCoTaskMem(4);
ref int x = ref Unsafe.AddByteOffset(ref Unsafe.NullRef<int>(), ptr);
Console.WriteLine(Unsafe.IsNullRef(ref x)); // False と表示される
Marshal.FreeCoTaskMem(ptr);
しかし、以下のように意味のない for 文を追加すると、コードの最適化が有効な場合のみ True と表示されます。
for (int i = 0; i < 0; i++) { } // 意味のない for 文
IntPtr ptr = Marshal.AllocCoTaskMem(4);
ref int x = ref Unsafe.AddByteOffset(ref Unsafe.NullRef<int>(), ptr);
Console.WriteLine(Unsafe.IsNullRef(ref x)); // 最適化が有効な場合のみ True と表示される
Marshal.FreeCoTaskMem(ptr);
ただし、意味のない for 文があっても
Unsafe.AddByteOffset(ref Unsafe.NullRef<int>(), ptr)
→ Unsafe.SubtractByteOffset(ref Unsafe.NullRef<int>(), -(nint)ptr)
のように書き換えると常に False と表示されるようになります。
なぜこのようなことが起こるのかさっぱり見当がつかないので、お知恵を拝借できないでしょうか。
私の環境を分かる範囲で書くと以下のとおりですが、他に何か必要な情報があればお教えください。
Windows 10 Pro (21H2)
Microsoft Visual Studio Community 2022 (64 ビット) Version 17.1.1
コンソール アプリケーション、.NET 6.0
どうぞよろしくお願いいたします。
143: デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 21:23:35.68 ID:uT8cdwkS0(2/4) AAS
皆様、返信どうもありがとうございます。
いただいたアドバイスを元に色々と確認をしていて反応が遅くなってしまいました。
申し訳ありません。
>>138確認してみた所、C# では int は 32 ビットと決められているようです。
外部リンク:docs.microsoft.com
ただ、可読性を考えると 4 ではなく sizeof(int) と書くべきでした。
ご指摘どうもありがとうございました。
>>139Unsafe.NullRef<T>() と Marshal.ReadInt32(int) の実装を確認してみたところ、
おそらくその点は問題ないかと思います。
外部リンク[cs]:github.com
外部リンク[cs]:github.com
しかし、問題の原因は大抵こういう思い込みに隠れているものだと思うので、
可能性を一つ潰すことができてとても助かりました。
>>140そうなんですよね。
for (int i = 0; i < 0; i++) {} は i++ に到達しないのは明らかだから
最適化でまるっと消えてしまうかと思っていたので、この結果には驚きました。
>>141
書いていただいたコードを試してみたところ、確かに最適化が有効でも期待通りの動作になりました。
それからもう一つ、書いていただいたコードを使わない場合、
プラットフォームが x86 と x64 の両方とも最適化有効時には期待通りの動作をしないことが分かりました。
(x86 の場合は常に期待通りに動作するならば問題の原因について一つ仮説が立てられるかと思ったのですが、
実際は違っていたので未だに原因はさっぱり見当がついていません…)
144(1): デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 21:24:27.40 ID:uT8cdwkS0(3/4) AAS
>>142142(1): デフォルトの名無しさん (ワッチョイ 1232-IMun) [sage] 2022/03/15(火) 21:00:09.00 ID:ZTE9InWz0(1/3) AAS
>>137
JIT最適化を抑制せずF11でデバッグして逆アセンブルすると
先にConsole.WriteLine(Boolean)がTrue固定で呼び出されて
その後にMarshal.IsNullOrWin32Atom(IntPtr)となってるね
未定義動作はタイムトラベルを…彷彿とさせるがC#なんだよなぁ
MSIL的にも&とnative intの結果型は&に定められている筈だし
とはいえ、管理下ポインタはnullになりえないとか有った気もするし
null参照への演算が未定義なら道理なのかもしれない、あるいはバグか
null参照への演算が未定義というのは、AddByteOffset メソッド内の話でしょうか。
下記のページの AddByteOffset<T>(ref T, IntPtr) のところをみると
ldarg.0
ldarg.1
add
ret
とコメントされていて、少なくとも IL 的には一つ目の引数が null でも特に問題はないように思えてしまいます。
IL 的に問題がないかどうかは私は自信がないのですが、
もし IL に問題がないのに JIT 最適化で問題が起きてしまうとすれば、
バグと考えてもよいのでしょうか。
参考
外部リンク[cs]:github.com
149(1): デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 22:07:11.34 ID:uT8cdwkS0(4/4) AAS
>>145そうですね。アドバイスありがとうございます。
私はこれまでこの手の問題は「まずは自分を疑え」と教わってきて
実際それで大抵の問題は解決してきたのですが、
皆様のお話をうかがうに、今回は自分の勘違いではなく
よそに原因があると考えたほうがいいのかもしれません。
これまで報告等は経験がないのですが、
それも視野に入れて調べてみたいと思います。
>>146勉強になります。
その特殊な最適化で結果が変わることがバグなのか
私の力では判断が難しそうなのが歯がゆいところです。
>>147147(1): 142 (ワッチョイ 1232-IMun) [sage] 2022/03/15(火) 21:41:55.33 ID:ZTE9InWz0(2/3) AAS
あー同じような判定コードを読み間違えた
タイムトラベルは起きてないけど、代入もヌル判定もなくなってる感じかな
>>144
そう、片方が0なら実質意味はないから加算命令が現れなくても不思議ではないけど
Console.WriteLine(x);としてみても固定アドレス0をデリファレンスするので
xが常にぬるぽ扱いになってしまっている様ではある
C#的には未定義なら未定義でビルド時か実行時にエラーを出しそうだけど
Unsafeでチェックされないのか判断に困る。さもなければ最適化のバグだと思う
> そう、片方が0なら実質意味はないから加算命令が現れなくても不思議ではないけど
> Console.WriteLine(x);としてみても固定アドレス0をデリファレンスするので
私の理解不足で、
「片方が0なら実質意味はない」 ← 0 + p の計算は必要ない?そりゃそうだ!
「固定アドレス0をデリファレンスする」 ← 0 + p = 0 ということ?p ではなくて?
という感想を持ってしまいました。
申し訳ないのですが、もしよろしければもう少し詳しく説明していただけないでしょうか。
> C#的には未定義なら未定義でビルド時か実行時にエラーを出しそうだけど
> Unsafeでチェックされないのか判断に困る。さもなければ最適化のバグだと思う
ご意見どうもありがとうございます。とても共感させていただきました。
未定義であればこそデバッグ時に教えてほしいものですが、
実際にはデバッグ時には期待通りに動作してしまうのが厄介なところです。
上下前次1-新書関写板覧索設栞歴
スレ情報 赤レス抽出 画像レス抽出 歴の未読スレ AAサムネイル
ぬこの手 ぬこTOP 0.167s*