VB.NET中的Cint(Long)在32位和64位环境中的行为不同
-
03-10-2019 - |
题
今天,我在将长(INT64)转换为整数(INT32)时遇到了问题。问题是我的代码总是在32位环境中工作,但是当我尝试时 相同 可在64位计算机中执行,它使用System.OverFlowException异常崩溃。
我在一个带有默认设置的新项目中,在Visual Studio 2008中准备了此测试代码:
Module Module1
Sub Main()
Dim alpha As Long = -1
Dim delta As Integer
Try
delta = CInt(alpha And UInteger.MaxValue)
Console.WriteLine("CINT OK")
delta = Convert.ToInt32(alpha And UInteger.MaxValue)
Console.WriteLine("Convert.ToInt32 OK")
Catch ex As Exception
Console.WriteLine(ex.GetType().ToString())
Finally
Console.ReadLine()
End Try
End Sub
End Module
在我的32位设置(Windows XP SP3 32位和Windows 7 32位)上,它打印到“ Cint OK”,但我已经测试过的64位计算机(Windows 7 64位) 相同 可执行文件它仅打印异常名称。
这种行为记录了吗?我试图找到参考,但我惨败。
作为参考,我离开 CIL 代码也:
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 88 (0x58)
.maxstack 2
.locals init ([0] int64 alpha,
[1] int32 delta,
[2] class [mscorlib]System.Exception ex)
IL_0000: nop
IL_0001: ldc.i4.m1
IL_0002: conv.i8
IL_0003: stloc.0
IL_0004: nop
.try
{
.try
{
IL_0005: ldloc.0
IL_0006: ldc.i4.m1
IL_0007: conv.u8
IL_0008: and
IL_0009: conv.ovf.i4
IL_000a: stloc.1
IL_000b: ldstr "CINT OK"
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: nop
IL_0016: ldloc.0
IL_0017: ldc.i4.m1
IL_0018: conv.u8
IL_0019: and
IL_001a: call int32 [mscorlib]System.Convert::ToInt32(int64)
IL_001f: stloc.1
IL_0020: ldstr "Convert.ToInt32 OK"
IL_0025: call void [mscorlib]System.Console::WriteLine(string)
IL_002a: nop
IL_002b: leave.s IL_0055
} // End .try
catch [mscorlib]System.Exception
{
IL_002d: dup
IL_002e: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_0033: stloc.2
IL_0034: nop
IL_0035: ldloc.2
IL_0036: callvirt instance class [mscorlib]System.Type [mscorlib]System.Exception::GetType()
IL_003b: callvirt instance string [mscorlib]System.Type::ToString()
IL_0040: call void [mscorlib]System.Console::WriteLine(string)
IL_0045: nop
IL_0046: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_004b: leave.s IL_0055
} // End handler
} // End .try
finally
{
IL_004d: nop
IL_004e: call string [mscorlib]System.Console::ReadLine()
IL_0053: pop
IL_0054: endfinally
} // End handler
IL_0055: nop
IL_0056: nop
IL_0057: ret
} // End of method Module1::Main
我怀疑行为不同的指示是Conv.ovf.i4或LDC.I4.M1/Conv.u8对。
到底是怎么回事?
Convert.ToInt32(long)
在两个环境中都失败。表现不同的是cint(长)。
解决方案
不幸的是,64位版本是准确的。它确实是一个溢出,表达式的结果是长度为&hffffffff的长度。符号位是降低值的,不再是负面的。结果值不能转换为整数,最大整数值为&h7fffffff。您可以通过将此代码添加到您的片段:
Dim value As Long = alpha And UInteger.MaxValue
Console.WriteLine(value)
输出:4294967295
X64抖动使用一种完全不同的方法来检查溢出的方式,它不依赖CPU溢出异常,而是明确将值与Integer.maxvalue和integer.minvalue进行比较。 X86抖动弄错了,它过多地优化了代码,最终进行了无签名的操作,而不会触发CPU异常。
在connect.microsoft.com上提交错误报告可能不值得付出努力,为x86抖动解决此问题将是一个巨大的变化。您将必须重新恢复此逻辑。不确定如何,我看不到您要做什么。
其他提示
我不知道有任何真实的参考,但是如果您转到此页面:
http://msdn.microsoft.com/en-us/library/system.int32.aspx
您可以在样本中看到他们使用的地方 CInt
他们确实将其包裹在 OverflowException
处理程序(尝试搜索 CInt
在该页面上找到它)。所以至少他们暗中说 CInt
可以在某些情况下扔掉。
如果您不希望被抛出的例外,则可以更改 Remove integer overflow checks
在高级编译选项页面上设置。
尝试将构建平台目标从“任何CPU”更改为“ X86”。
只是为了完成此问题的文档,我做到了:
Imports System.Runtime.InteropServices
Module Module1
<DllImport("KERNEL32.DLL", EntryPoint:="DebugBreak", _
SetLastError:=False, CharSet:=CharSet.Unicode, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Sub DebugBreak()
End Sub
Sub Main()
Dim alpha As Long = -1
Dim delta As Integer
DebugBreak() ' To call OllyDbg
' Needed to prevent the jitter from raising the overflow exception in the second CInt without really doing the convertion first
alpha = alpha Xor Environment.TickCount
Console.WriteLine(alpha)
delta = CInt(alpha And UInteger.MaxValue)
Console.WriteLine(delta)
alpha = alpha And UInteger.MaxValue
delta = CInt(alpha)
Console.WriteLine(delta)
Console.ReadLine()
End Sub
End Module
使用ollydbg我得到了:
CPU Disasm
Address Hex dump Command Comments
00D10070 55 PUSH EBP
00D10071 8BEC MOV EBP,ESP
00D10073 57 PUSH EDI
00D10074 56 PUSH ESI
00D10075 53 PUSH EBX
00D10076 E8 A1BFC7FF CALL 0098C01C
00D1007B E8 A18C1879 CALL <JMP.&KERNEL32.GetTickCount> ; Jump to KERNEL32.GetTickCount
00D10080 99 CDQ
00D10081 F7D0 NOT EAX
00D10083 F7D2 NOT EDX
00D10085 8BF0 MOV ESI,EAX
00D10087 8BFA MOV EDI,EDX
00D10089 E8 62D25D78 CALL 792ED2F0 ; Called everytime Console is referenced here
00D1008E 57 PUSH EDI
00D1008F 56 PUSH ESI
00D10090 8BC8 MOV ECX,EAX
00D10092 8B01 MOV EAX,DWORD PTR DS:[ECX]
00D10094 FF90 C4000000 CALL DWORD PTR DS:[EAX+0C4] ; Console.WriteLine(Int64)
00D1009A 8BDE MOV EBX,ESI ; Note: EDI:ESI holds alpha variable
00D1009C 83E3 FF AND EBX,FFFFFFFF ; delta = CInt(alpha And UInteger.MaxValue)
00D1009F E8 4CD25D78 CALL 792ED2F0
00D100A4 8BC8 MOV ECX,EAX
00D100A6 8BD3 MOV EDX,EBX
00D100A8 8B01 MOV EAX,DWORD PTR DS:[ECX]
00D100AA FF90 BC000000 CALL DWORD PTR DS:[EAX+0BC] ; Console.WriteLine(Int32)
00D100B0 33FF XOR EDI,EDI ; alpha = alpha And UInteger.MaxValue
00D100B2 85F6 TEST ESI,ESI ; delta = CInt(alpha) [Begins here]
00D100B4 7C 06 JL SHORT 00D100BC
00D100B6 85FF TEST EDI,EDI
00D100B8 75 2B JNE SHORT 00D100E5
00D100BA EB 05 JMP SHORT 00D100C1
00D100BC 83FF FF CMP EDI,-1
00D100BF 75 24 JNE SHORT 00D100E5
00D100C1 8BDE MOV EBX,ESI ; delta = CInt(alpha) [Ends here]
00D100C3 E8 28D25D78 CALL 792ED2F0
00D100C8 8BC8 MOV ECX,EAX
00D100CA 8BD3 MOV EDX,EBX
00D100CC 8B01 MOV EAX,DWORD PTR DS:[ECX]
00D100CE FF90 BC000000 CALL DWORD PTR DS:[EAX+0BC] ; Console.WriteLine(Int32)
00D100D4 E8 1B1AA878 CALL 79791AF4
00D100D9 8BC8 MOV ECX,EAX
00D100DB 8B01 MOV EAX,DWORD PTR DS:[ECX]
00D100DD FF50 64 CALL DWORD PTR DS:[EAX+64]
00D100E0 5B POP EBX
00D100E1 5E POP ESI
00D100E2 5F POP EDI
00D100E3 5D POP EBP
00D100E4 C3 RETN
如您所见,第二个cint句子比仅仅是安丁(Anding)要复杂得多(实际上可以抑制它,因为EBX不会改变,并且在任何地方都不会消耗eflag)。可以在 汉斯的回答