指针偏移导致溢出
Pointer offset causes overflow
以下结果对我来说没有任何意义。看起来在执行加法或减法之前将负偏移量转换为无符号。
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
//}
}
}
虽然使用负偏移量可能看起来很奇怪,但它有一些用例。在我的例子中,它反映了二维数组中的边界条件。
当然,溢出并没有太大的伤害,因为我可以使用未检查的或通过将值的符号取反并将其应用于操作数的模数来将值的符号移动到操作中。
但这种行为似乎没有记录。根据 MSDN,我认为负偏移不会有问题:
You can add a value n of type int, uint, long, or ulong to a pointer, p,of any type except void*. The result p+n is the pointer resulting from adding n * sizeof(p) to the address of p. Similarly, p-n is the pointer resulting from subtracting n * sizeof(p) from the address of p.
此问题已在 roslyn\RyuJIT 问题跟踪器中以各种形式多次提出。第一次发现可以看这里:When adding integer to pointer in checked context add.ovf.un instruction is generated
事实上,如果您查看生成的 IL,您会发现 add.ovf.un
("add unsigned integers with overflow check") 指令是在已检查的上下文中发出的,而不是在未检查的上下文中发出的。在我们的例子中,此函数的第一个操作数是无符号本机 int(UIntPtr
类型),表示 double*
指针。第二个操作数在那个问题(2015 年)和现在是不同的。
在该问题出现时,第二个操作数是 Int32
,正如您所期望的那样。但是,使用 UIntPtr
和 Int32
执行 add.ovf.un
在 x86 和 x64 中表现不同。在 x86 中,它抛出溢出异常(对于负数),因为,好吧,第二个操作数是负数。但是,在 x64 中,JIT 会将 Int32
零扩展为 64 位(因为本机指针现在是 64 位)。它将对其进行零扩展,因为它假定它是无符号的。但是负 Int32
的零扩展将导致大 正 64 位整数。
结果,如果在x64中给指针加上负数Int32
,在出现上述问题的时候,并不会抛出溢出异常,而是给指针加上了错误的值,这当然是更糟。
问题已关闭 "won't fix":
Thanks for the detailed report here!
Given the narrow scope of the bug though and that the behavior is
consistent with the native compiler we are "won't fixing" the bug at
this time.
然而,人们对所描述的 x64 行为并不十分满意,使用它可以在没有意识到的情况下悄悄地生成指向未知位置的指针。经过长时间的辩论,这个问题在 2017 年作为 this issue.
的一部分得到了解决
修复是强制将 Int32
转换为 IntPtr
,当您将它添加到指针时,在已检查的上下文中。这样做是为了防止上述 Int32
在 x64 中自动扩展。
因此,如果您现在查看为您的案例生成的 IL,您会发现在传递给 add.ovf.un
之前,Int32
现在已使用 conv.i
转换为 IntPtr
IL指令。这导致在 x86 和 x64 上向已检查上下文中的指针添加负整数总是抛出溢出异常。
在任何情况下,在检查的上下文中为指针添加发出 add.ovf.un
的原始问题没有解决,而且很可能不会解决,因为它被关闭为 "won't fix",所以你有意识到这一点并自己决定如何在特定情况下克服它。
以下结果对我来说没有任何意义。看起来在执行加法或减法之前将负偏移量转换为无符号。
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
//}
}
}
虽然使用负偏移量可能看起来很奇怪,但它有一些用例。在我的例子中,它反映了二维数组中的边界条件。
当然,溢出并没有太大的伤害,因为我可以使用未检查的或通过将值的符号取反并将其应用于操作数的模数来将值的符号移动到操作中。
但这种行为似乎没有记录。根据 MSDN,我认为负偏移不会有问题:
You can add a value n of type int, uint, long, or ulong to a pointer, p,of any type except void*. The result p+n is the pointer resulting from adding n * sizeof(p) to the address of p. Similarly, p-n is the pointer resulting from subtracting n * sizeof(p) from the address of p.
此问题已在 roslyn\RyuJIT 问题跟踪器中以各种形式多次提出。第一次发现可以看这里:When adding integer to pointer in checked context add.ovf.un instruction is generated
事实上,如果您查看生成的 IL,您会发现 add.ovf.un
("add unsigned integers with overflow check") 指令是在已检查的上下文中发出的,而不是在未检查的上下文中发出的。在我们的例子中,此函数的第一个操作数是无符号本机 int(UIntPtr
类型),表示 double*
指针。第二个操作数在那个问题(2015 年)和现在是不同的。
在该问题出现时,第二个操作数是 Int32
,正如您所期望的那样。但是,使用 UIntPtr
和 Int32
执行 add.ovf.un
在 x86 和 x64 中表现不同。在 x86 中,它抛出溢出异常(对于负数),因为,好吧,第二个操作数是负数。但是,在 x64 中,JIT 会将 Int32
零扩展为 64 位(因为本机指针现在是 64 位)。它将对其进行零扩展,因为它假定它是无符号的。但是负 Int32
的零扩展将导致大 正 64 位整数。
结果,如果在x64中给指针加上负数Int32
,在出现上述问题的时候,并不会抛出溢出异常,而是给指针加上了错误的值,这当然是更糟。
问题已关闭 "won't fix":
Thanks for the detailed report here!
Given the narrow scope of the bug though and that the behavior is consistent with the native compiler we are "won't fixing" the bug at this time.
然而,人们对所描述的 x64 行为并不十分满意,使用它可以在没有意识到的情况下悄悄地生成指向未知位置的指针。经过长时间的辩论,这个问题在 2017 年作为 this issue.
的一部分得到了解决修复是强制将 Int32
转换为 IntPtr
,当您将它添加到指针时,在已检查的上下文中。这样做是为了防止上述 Int32
在 x64 中自动扩展。
因此,如果您现在查看为您的案例生成的 IL,您会发现在传递给 add.ovf.un
之前,Int32
现在已使用 conv.i
转换为 IntPtr
IL指令。这导致在 x86 和 x64 上向已检查上下文中的指针添加负整数总是抛出溢出异常。
在任何情况下,在检查的上下文中为指针添加发出 add.ovf.un
的原始问题没有解决,而且很可能不会解决,因为它被关闭为 "won't fix",所以你有意识到这一点并自己决定如何在特定情况下克服它。