指针偏移导致溢出

时间:2018-03-26 18:22:04

标签: c# unsafe

以下结果对我没有任何意义。看起来在执行加法或减法之前,负偏移被转换为无符号。

double[] x = new double[1000];

int i = 1; // for the overflow it makes no difference if it is long, int or short
int j = -1;

unsafe 
{
    fixed (double* px = x)                  
    {       
        double* opx = px+500;       // = 0x33E64B8

        //unchecked
        //{
            double* opx1 = opx+i;   // = 0x33E64C0
            double* opx2 = opx-i;   // = 0x33E64B0
            double* opx3 = opx+j;   // = 0x33E64B0 if unchecked; throws overflow exception if checked
            double* opx4 = opx-j;   // = 0x33E64C0 if unchecked; throws overflow exception if checked
        //}
    }
}

虽然使用负偏移可能看起来很奇怪,但它有用例。在我的例子中,它反映了二维阵列中的边界条件。

当然,溢出不会造成太大的伤害,因为我可以使用unchecked或通过反转并将值应用于操作数的模数来将值的符号移动到操作。

但这种行为似乎没有记载。根据{{​​3}},我不认为负补偿是有问题的:

  

您可以将int,uint,long或ulong类型的值n添加到除void *之外的任何类型的指针p中。结果p + n是通过将n * sizeof(p)添加到p的地址而得到的指针。类似地,p-n是从p的地址中减去n * sizeof(p)得到的指针。

1 个答案:

答案 0 :(得分:5)

此问题在roslyn \ RyuJIT问题跟踪器中以各种形式多次提出。我第一次发现你可以在这里看到:When adding integer to pointer in checked context add.ovf.un instruction is generated

实际上,如果查看生成的IL,您将看到add.ovf.un(“使用溢出检查添加无符号整数”)指令在已检查的上下文中发出,但未在未检查的上下文中发出。在我们的例子中,这个函数的第一个操作数是无符号的native int(种类为UIntPtr),代表double*指针。在该问题(2015年)和现在,第二个操作数是不同的。

在该问题出现时,第二个操作数为Int32,正如您所期望的那样。但是,add.ovf.un UIntPtrInt32在x86和x64中的行为方式不同。在x86中,它抛出溢出异常(对于否定),因为第二个操作数是负的。但是,在x64中,JIT会将Int32零扩展到64位(因为本机指针现在是64位)。它会对它进行零扩展,因为它假定它是无符号的。但负数Int32的零延伸将导致大的 64位整数。

结果,如果在x64中向指针添加负Int32,在上述问题发生时,它不会抛出溢出异常,而是会向指针添加错误值,这当然更糟糕

问题以“无法修复”结束:

  

感谢此处的详细报告!

     

鉴于bug的范围很窄,而且行为是   与本机编译器一致,我们“不会修复”该错误   这一次。

然而,人们对x64所描述的行为并不满意,使用它可以默默地生成指向未知位置的指针,而不会意识到这一点。经过长时间的辩论,这个问题在2017年作为this issue的一部分得到了解决。

修复是强制转换Int32IntPtr,当您将其添加到指针时,在已检查的上下文中。这样做是为了防止在x64中自动扩展上述Int32

因此,如果您现在查看针对您的案例生成的IL,您会看到在传递给add.ovf.un之前,Int32现已通过IntPtr IL指令转换为conv.i 。这会导致在已检查的上下文中向指针添加负整数,以便始终在x86和x64上抛出溢出异常。

在任何情况下,在检查的上下文中添加指针的原始问题add.ovf.un都没有解决,并且很可能无法解决,因为它被关闭为“将无法修复”,所以你必须请注意这一点并自行决定如何在特定情况下克服这一点。