[過去ログ] C#, C♯, C#相談室 Part96 (1002レス)
前次1-
抽出解除 必死チェッカー(本家) (べ) 自ID レス栞 あぼーん

このスレッドは過去ログ倉庫に格納されています。
次スレ検索 歴削→次スレ 栞削→次スレ 過去ログメニュー
142
(1): デフォルトの名無しさん (ワッチョイ 1232-IMun) [sage] 2022/03/15(火) 21:00:09.00 ID:ZTE9InWz0(1/3) AAS
>>137
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

どうぞよろしくお願いいたします。
JIT最適化を抑制せずF11でデバッグして逆アセンブルすると
先にConsole.WriteLine(Boolean)がTrue固定で呼び出されて
その後にMarshal.IsNullOrWin32Atom(IntPtr)となってるね

未定義動作はタイムトラベルを…彷彿とさせるがC#なんだよなぁ
MSIL的にも&とnative intの結果型は&に定められている筈だし
とはいえ、管理下ポインタはnullになりえないとか有った気もするし
null参照への演算が未定義なら道理なのかもしれない、あるいはバグか
147
(1): 142 (ワッチョイ 1232-IMun) [sage] 2022/03/15(火) 21:41:55.33 ID:ZTE9InWz0(2/3) AAS
あー同じような判定コードを読み間違えた
タイムトラベルは起きてないけど、代入もヌル判定もなくなってる感じかな

>>144
144(1): デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 21:24:27.40 ID:uT8cdwkS0(3/4) AAS
>>142
null参照への演算が未定義というのは、AddByteOffset メソッド内の話でしょうか。
下記のページの AddByteOffset<T>(ref T, IntPtr) のところをみると

ldarg.0
ldarg.1
add
ret

とコメントされていて、少なくとも IL 的には一つ目の引数が null でも特に問題はないように思えてしまいます。
IL 的に問題がないかどうかは私は自信がないのですが、
もし IL に問題がないのに JIT 最適化で問題が起きてしまうとすれば、
バグと考えてもよいのでしょうか。

参考
外部リンク[cs]:github.com
そう、片方が0なら実質意味はないから加算命令が現れなくても不思議ではないけど
Console.WriteLine(x);としてみても固定アドレス0をデリファレンスするので
xが常にぬるぽ扱いになってしまっている様ではある

C#的には未定義なら未定義でビルド時か実行時にエラーを出しそうだけど
Unsafeでチェックされないのか判断に困る。さもなければ最適化のバグだと思う
152
(2): デフォルトの名無しさん (ワッチョイ 1232-IMun) [sage] 2022/03/15(火) 23:25:37.07 ID:ZTE9InWz0(3/3) AAS
>>149
149(1): デフォルトの名無しさん (ワッチョイ a236-8qwV) [sage] 2022/03/15(火) 22:07:11.34 ID:uT8cdwkS0(4/4) AAS
>>145
そうですね。アドバイスありがとうございます。

私はこれまでこの手の問題は「まずは自分を疑え」と教わってきて
実際それで大抵の問題は解決してきたのですが、
皆様のお話をうかがうに、今回は自分の勘違いではなく
よそに原因があると考えたほうがいいのかもしれません。

これまで報告等は経験がないのですが、
それも視野に入れて調べてみたいと思います。

>>146
勉強になります。
その特殊な最適化で結果が変わることがバグなのか
私の力では判断が難しそうなのが歯がゆいところです。

>>147
> そう、片方が0なら実質意味はないから加算命令が現れなくても不思議ではないけど
> Console.WriteLine(x);としてみても固定アドレス0をデリファレンスするので
私の理解不足で、
「片方が0なら実質意味はない」 ← 0 + p の計算は必要ない?そりゃそうだ!
「固定アドレス0をデリファレンスする」 ← 0 + p = 0 ということ?p ではなくて?
という感想を持ってしまいました。
申し訳ないのですが、もしよろしければもう少し詳しく説明していただけないでしょうか。

> C#的には未定義なら未定義でビルド時か実行時にエラーを出しそうだけど
> Unsafeでチェックされないのか判断に困る。さもなければ最適化のバグだと思う
ご意見どうもありがとうございます。とても共感させていただきました。
未定義であればこそデバッグ時に教えてほしいものですが、
実際にはデバッグ時には期待通りに動作してしまうのが厄介なところです。
件の部分はJITコンパイルされたマシンコードの話で、ref intを参照する際に
xからでなくても「mov ecx, dword ptr [ptr]」的であっても整合性は取れるが
逆アセが「mov ecx, dword ptr [0]」になるので、xはnullにされてるという話

それならばIsNullRefが消えるのは順当としても、xがnullになる代入の欠落が
NullRef<int>が宜しくないのか、それをAddByteOffsetに与えるのがダメなのか
単なるバグか…、Unsafe.AsRefに直接IntPtr渡せれば/unsafeとせず済むのにね
前次1-
スレ情報 赤レス抽出 画像レス抽出 歴の未読スレ AAサムネイル

ぬこの手 ぬこTOP 0.027s