C#中的64位指针算法,检查算术溢出的变化行为

Mag*_*ell 3 c# math 64-bit

我有一些不安全的C#代码,它byte*在64位机器上运行的大型内存块上执行指针算法.它在大多数情况下都能正常工作,但是当事情变得很大时,我常常会在指针不正确的情况下出现某种腐败现象.

奇怪的是,如果我打开"检查算术溢出/下溢"一切正常.我没有任何溢出异常.但由于性能大,我需要在没有此选项的情况下运行代码.

什么可能导致这种行为上的差异?

小智 6

这里检查和未检查之间的区别实际上是IL中的一个错误,或者只是一些不良的源代码(我不是语言专家所以我不会评论C#编译器是否为该ambigious生成正确的IL源代码).我使用4.0 #30319.1版本的C#编译器编译了这个测试代码(虽然2.0版本似乎做了同样的事情).我使用的命令行选项是:/ o +/unsafe/debug:pdbonly.

对于未经检查的块,我们有这个IL代码:

//000008:     unchecked
//000009:     {
//000010:         Console.WriteLine("{0:x}", (long)(testPtr + offset));
  IL_000a:  ldstr      "{0:x}"
  IL_000f:  ldloc.0
  IL_0010:  ldloc.1
  IL_0011:  add
  IL_0012:  conv.u8
  IL_0013:  box        [mscorlib]System.Int64
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
Run Code Online (Sandbox Code Playgroud)

在IL偏移11处,add获得2个操作数,一个是字节*,另一个是uint32.根据CLI规范,这些实际上分别归一化为native int和int32.根据CLI规范(准确的分区III),结果将是native int.因此,必须将secodn操作数提升为native int类型.根据规范,这是通过符号扩展来完成的.所以uint.MaxValue(带符号表示法为0xFFFFFFFF或-1)符号扩展为0xFFFFFFFFFFFFFFFF.然后添加2个操作数(0x0000000008000000L +( - 1L)= 0x0000000007FFFFFFL).转换操作码仅用于验证目的,以将原生int转换为int64,在生成的代码中为int.

现在对于已检查的块,我们有这个IL:

//000012:     checked
//000013:     {
//000014:         Console.WriteLine("{0:x}", (long)(testPtr + offset));
  IL_001d:  ldstr      "{0:x}"
  IL_0022:  ldloc.0
  IL_0023:  ldloc.1
  IL_0024:  add.ovf.un
  IL_0025:  conv.ovf.i8.un
  IL_0026:  box        [mscorlib]System.Int64
  IL_002b:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
Run Code Online (Sandbox Code Playgroud)

除了add和conv操作码之外,它实际上是相同的.对于添加操作码,我们添加了2个'后缀'.第一个是".ovf"后缀,它有一个明显的含义:检查溢出,但它也需要'启用第二个后缀:".un".(即没有"add.un",只有"add.ovf.un").".un"有2个效果.最明显的一点就是完成了额外的溢出检查,就好像操作数是无符号整数一样.从我们的CS类回来的时候,希望我们都记得,由于二进制补码二进制编码,有符号加法和无符号加法是相同的,所以".un"真的只影响溢出检查,对吧?

错误.

请记住,在IL堆栈上我们没有2个64位数字,我们有一个int32和一个native int(在规范化之后).那么".un"意味着从int32到native的转换被视为"conv.u"而不是上面的默认"conv.i".因此uint.MaxValue为零扩展为0x00000000FFFFFFFFL.然后正确添加产生0x0000000107FFFFFFL.conv操作码确保无符号操作数可以表示为有符号的int64(它可以).

你的修复只能找到64位.在IL级别,更正确的修复方法是将uint32操作数显式转换为native int或unsigned native int,然后检查和取消选中对于32位和64位都是相同的.