Cint (طويل) في vb.net يتصرف بشكل مختلف في بيئات 32 و 64 بت

StackOverflow https://stackoverflow.com/questions/2783826

  •  03-10-2019
  •  | 
  •  

سؤال

اليوم واجهت مشكلة في تحويل فترة طويلة (int64) إلى عدد صحيح (int32). المشكلة هي أن الكود الخاص بي كان يعمل دائمًا في بيئات 32 بت ، لكن عندما أحاول نفس الشيء قابلة للتنفيذ في جهاز كمبيوتر 64 بت ، وهو يعطل مع نظام. 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-bit و Windows 7 32 بت) ، يطبع إلى "Cint OK" ، ولكن في الكمبيوتر 64 بت (Windows 7 64-bit) الذي اختبرته نفس الشيء القابل للتنفيذ يطبع اسم الاستثناء فقط.

هل هذا السلوك موثق؟ حاولت العثور على مرجع ، لكنني فشلت فشلاً ذريعًا.

للرجوع أترك 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. البت الإشارة هو من القيمة ، لم يعد سلبيا. لا يمكن تحويل القيمة الناتجة إلى عدد صحيح ، وهي أقصى قيمة عدد صحيح IS و H7FFFFFF. يمكنك رؤية هذا عن طريق إضافة هذا الرمز إلى المقتطف الخاص بك:

 Dim value As Long = alpha And UInteger.MaxValue
 Console.WriteLine(value)

الإخراج: 4294967295

يستخدم Jitter X64 طريقة مختلفة تمامًا للتحقق من الفائض ، ولا يعتمد على استثناء الفائض في وحدة المعالجة المركزية ولكنه يقارن بشكل صريح القيم إلى integer.maxvalue و integer.minvalue. إن ارتعاش x86 يخطئ ، فإنه يحسن الكود أكثر من اللازم وينتهي به الأمر إلى إجراء عملية غير موقعة لا ترحل استثناء وحدة المعالجة المركزية.

من المحتمل ألا يستحق تقديم تقرير الأخطاء على connect.microsoft.com الجهد ، فإن إصلاح هذا لارتعاش X86 سيكون تغييرًا كبيرًا بشكل كبير. سيكون عليك إعادة صياغة هذا المنطق. لست متأكدًا من كيف ، لا أرى ما تحاول القيام به.

نصائح أخرى

لا أعرف أي إشارة حقيقية على هذا النحو ، ولكن إذا ذهبت إلى هذه الصفحة:

http://msdn.microsoft.com/en-us/library/system.int32.aspx

يمكنك أن ترى في العينة حيث يستخدمون CInt يلفونه في أ OverflowException معالج (حاول البحث عن CInt في تلك الصفحة للعثور عليها). لذلك على الأقل يقولون ضمنيًا CInt يمكن أن ترمي ذلك في ظروف معينة.

إذا كنت لا تريد أن يتم إلقاء الاستثناءات ، يمكنك تغيير Remove integer overflow checks الإعداد على صفحة خيارات التجميع المتقدمة.

حاول تغيير هدف منصة البناء من "أي وحدة المعالجة المركزية" إلى "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 الثانية أكثر تعقيدًا من العادل (والتي يمكن قمعها فعليًا لأن EBX لن يتغير ولا يتم استهلاك eFlags في أي مكان). يمكن رؤية الأصل المحتمل لهذه المشكلة في إجابة هانز

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top