以下结果对我没有任何意义。看起来在执行加法或减法之前,负偏移被转换为无符号。
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)得到的指针。
答案 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
UIntPtr
和Int32
在x86和x64中的行为方式不同。在x86中,它抛出溢出异常(对于否定),因为第二个操作数是负的。但是,在x64中,JIT会将Int32
零扩展到64位(因为本机指针现在是64位)。它会对它进行零扩展,因为它假定它是无符号的。但负数Int32
的零延伸将导致大的正 64位整数。
结果,如果在x64中向指针添加负Int32
,在上述问题发生时,它不会抛出溢出异常,而是会向指针添加错误值,这当然更糟糕
问题以“无法修复”结束:
感谢此处的详细报告!
鉴于bug的范围很窄,而且行为是 与本机编译器一致,我们“不会修复”该错误 这一次。
然而,人们对x64所描述的行为并不满意,使用它可以默默地生成指向未知位置的指针,而不会意识到这一点。经过长时间的辩论,这个问题在2017年作为this issue的一部分得到了解决。
修复是强制转换Int32
到IntPtr
,当您将其添加到指针时,在已检查的上下文中。这样做是为了防止在x64中自动扩展上述Int32
。
因此,如果您现在查看针对您的案例生成的IL,您会看到在传递给add.ovf.un
之前,Int32
现已通过IntPtr
IL指令转换为conv.i
。这会导致在已检查的上下文中向指针添加负整数,以便始终在x86和x64上抛出溢出异常。
在任何情况下,在检查的上下文中添加指针的原始问题add.ovf.un
都没有解决,并且很可能无法解决,因为它被关闭为“将无法修复”,所以你必须请注意这一点并自行决定如何在特定情况下克服这一点。