指针偏移导致溢出

oli*_*ver 12 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
        //}
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

但这种行为似乎没有记录.根据MSDN,我不认为负偏移是有问题的:

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

Evk*_*Evk 5

这个问题在roslyn\RyuJIT问题跟踪器中以各种形式多次提出.我第一次发现你可以看到:在检查上下文中向指针添加整数时,会生成add.ovf.un指令

实际上,如果你看一下生成的IL,你会看到add.ovf.un("添加无符号整数和溢出检查")指令在检查的上下文中发出,但不在未经检查的上下文中.在我们的例子中,这个函数的第一个操作数是unsigned native int(kind of UIntPtr),表示double*指针.在该问题(2015年)和现在,第二个操作数是不同的.

在那个问题发生时,第二个操作数Int32就像你期望的那样.但是,在x86和x64中add.ovf.un使用UIntPtr和Int32表现不同.在x86中,它抛出溢出异常(对于否定),因为第二个操作数是负的.但是,在x64中,JIT会将其零扩展Int32到64位(因为本机指针现在是64位).它会对它进行零扩展,因为它假定它是无符号的.但负数的零延伸Int32将导致大的正 64位整数.

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

问题以"不会修复"结束:

感谢此处的详细报告!

鉴于bug的范围很窄,并且行为与本机编译器一致,我们此时"不会修复"该错误.

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

修复是在检查上下文中强制转换Int32为IntPtr将其添加到指针时.这样做是为了防止Int32在x64中自动扩展上述内容.

所以,如果你现在看的产生IL为你的情况,你会看到,经过之前add.ovf.un,Int32现在铸造到IntPtr与conv.iIL指令.这会导致在已检查的上下文中向指针添加负整数,以便始终在x86和x64上抛出溢出异常.

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