今回はちょっとシリーズを離れてオーバーフローのお話です。なんか似た話題を以前にやった気もしますが。
アンマネージドとやりとりしていると、異なる数値型間の変換が必要になる場面が出てきます。今回 Task クラスの実装を書いている場面でも、それが必要になる場面がありました。
アンマネージドとは DWORD つまり UInt32 でやりとりしますが、そのままでは意味を取りづらいので TimeSpan 型としてプロパティを定義し、内部で Int32 に変換するというやり方です。DWORD を あえて Int32 で書くのはまあ CLS 準拠を目指すための癖みたいなもんです。今考えるとこれが悩みの元になったわけなんですが。
そのアンマネージドのメソッドは 0 から 0xFFFFFFFFL まで受け入れるため、TimeSpan.TotalMilliseconds を 一旦 Int64 にキャストし、最大値チェックを行った後 Int32 にキャストし直そうという算段でした。
unchecked を使える C# はともかく、VB.NET での Int64 から Int32 へのキャストは CInt か Convert.ToInt32 になりますが、この二つはいずれもオーバーフローを抑止するオプションが存在しないため、変換元の値を確認して &H80000000L 以上だったら &H100000000L を引く、そして変換ってのがわかりやすい手段です。
この過程で、何を思ったか Int64 に And &HFFFFFFFFL の演算をしてやればどうだろう? という思考にたどり着きました。確かに 32bit にはなりますがだからって CInt したときオーバーフローするのには変わらないのに。
ところがどっこい、これが何故かエラーが出なかったのです。もとの Int64 に &HFFFFFFFEL が入っていた場合、CInt した結果普通に -2 になります。気持ち悪。ちなみに C# でも checked ステートメント内で同じ結果になります。
IL レベルでも問題なく conv.ovf.i4 が発行されているため、さっぱり原因がつかめません。いい加減煮詰まったので 2ch で聞いてみたら、いくつかやりとりがあった末に こういう結論に。つまり定数が 32bit で収まること、また And 演算のため計算結果も必ず 32 bit 以下になることから、定数の下位 32bit で演算するように最適化する。そのとき正しくは通るべきオーバーフローチェックがパスされてしまう、と。また Mono では普通にオーバーフローするらしいです。つきあってくださった方々、改めてありがとうございました。
And &HFFFFFFFFL 後の CInt が決してオーバーフローしないのなら利用価値はありますが、x64 版でどうなるか知れたものじゃないし Mono でも普通にオーバーフローするとなると使ってもバグの元になるだけですので封印封印。
まあそう言う顛末の話でした。
しかし VB.NET にもオーバーフローの制御とか三項演算子とか欲しいんですが。IIf は Object でのやりとりになるから IIf(Of T) があればいいんだけどなー。