c# - 指针偏移导致溢出

标签 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
        //}
    }
}

虽然使用负偏移量可能看起来很奇怪,但它有一些用例。在我的例子中,它反射(reflect)了二维数组中的边界条件。

当然,溢出并没有太大的伤害,因为我可以使用未检查的或通过将值的符号取反并将其应用于操作数的模数来将值的符号移动到操作中。

但这种行为似乎没有记录。根据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(“使用溢出检查添加无符号整数”)指令是在已检查的上下文中发出的,而不是在未检查的上下文中发出的。在我们的例子中,此函数的第一个操作数是无符号 native int(一种 UIntPtr),表示 double* 指针。第二个操作数在那个问题(2015 年)和现在是不同的。

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

结果,如果在x64中给指针加上负值Int32,在出现上述问题的时候,并不会抛出溢出异常,而是给指针加上了错误的值,这是当然更糟。

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

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 行为并不十分满意,因为 x64 可以在没有意识到的情况下默默地生成指向未知位置的指针。经过长时间的辩论,这个问题在 2017 年作为 this issue 的一部分得到了解决。 .

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

因此,如果您现在查看为您的案例生成的 IL,您将看到在传递给 add.ovf.un 之前,Int32 现在被转换为 IntPtrconv.i IL 指令。这导致在 x86 和 x64 上将负整数添加到已检查上下文中的指针总是抛出溢出异常。

在任何情况下,在检查的上下文中为指针添加发出 add.ovf.un 的原始问题没有解决,而且很可能不会解决,因为它被关闭为“不会fix”,因此您必须意识到这一点,并自行决定如何在您的特定场景中克服这个问题。

关于c# - 指针偏移导致溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49498158/

相关文章:

c# - 从 FORTRAN 77 代码返回值到 C#

c# - 为什么 Marshal.WriteInt64 方法的代码如此复杂?

c# - 为什么泛型类型不能有明确的布局?

c# - 帮助将 Reflector 解构翻译成可编译的代码

c# - 引入 "await"关键字后 WEB API 调用停止工作

c# split string in 2. 逐个字母

c# - 如何测试 EF 模型

c# - WCF 是长数据导入过程的正确选择吗?

c# - 对 int 中的单个字节进行操作的最快方法

c# - 为什么从 C# 程序调用 C 函数不需要不安全上下文?