【问题标题】:C#: unsafe pointer to empty array is null?C#:指向空数组的不安全指针为空?
【发布时间】:2013-06-10 01:59:13
【问题描述】:

unsafe 块中,我试图获取指向byte 数组的指针。但是根据数组的声明大小,我会得到不同的结果:

unsafe {

    byte[] bytes;

    bytes = new byte[1];
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints e.g. 41797644
    }

    bytes = new byte[0];
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 0 ?!
    }
}

如果我打开即时窗口并输入&bytes,我会得到字节数组的实际地址,包括空数组的情况。

为什么fixed 非托管指针的工作方式不同?

更新:

这是相同的代码以及我从即时窗口中得到的内容:

unsafe {
    byte[] bytes;
    bytes = new byte[1];
    fixed(void* pBytes = bytes)
    {
                       // bytes => 
                       // {byte[1]}
                       //    [0]: 0
                       //
                       // &bytes
                       // 0x0601c34c               //the address of the variable
                       //    bytes: 0x027dc804     //the address of the array
                       //
                       // pBytes
                       // 0x027dc80c               // notice pBytes == (&bytes + 8)
                       //     *pBytes: 0
    }

    bytes = new byte[0];
    fixed(void* pBytes = bytes)
    {
                       // bytes => 
                       // {byte[0]}
                       //
                       // &bytes
                       // 0x0601c34c               //same address of the variable, ofc
                       //    bytes: 0x02aa7ad4     //different address of (new) array 
                       //
                       // pBytes
                       // 0x00000000               // BOINK
                       //     *pBytes: Cannot dereference 'pBytes'. 
                       //              The pointer is not valid.
    }
}

数组对象的地址(&bytes)和数组指针之间的 8 字节差异由对象的标头解释。

内存中的数组表示为:

     type id  size     elem 0   elem1    ...
----|--------|--------|--------|--------|...
    ^ 4Bytes   4Bytes ^
    |                 `--< pBytes
    `--< &bytes

不安全的指针实际上指向实际数据的开始(即将编组到非托管上下文的数据)

有没有办法在代码中获取空数组的实际地址?

FWIW,我实际上需要它才能访问数组的标头,以便即时修改数组的运行时类型。

【问题讨论】:

  • 即时修改数组的运行时类型 - 为什么不创建一个新数组呢?似乎您的方法可能会破坏整个过程的类型安全。
  • 我知道我会为此大喊大叫 :) 好的,用例是在 byte[] 和 sbyte[] 之间即时转换。 (这几乎适用于强制转换,但像 Array.Copy() 这样的方法不喜欢混合 sbyte 和 byte 数组
  • 这个问题看起来像XY problem
  • @Rakkun 可能,但我认为这仍然是一个有趣的问题,我希望从中获得一些见解。
  • Henk 是正确的:在任何情况下,您都不应该像这样搞乱对象的内部状态。为了伪造 Array.Copy 而不是搞乱可能导致整个运行时崩溃的内部状态,为什么不编写自己的允许复制的 Array.Copy bytesbyte?

标签: c# pointers unsafe


【解决方案1】:

为什么固定的非托管指针的工作方式不同?

这是一个奇怪的问题。为什么你认为它应该?

约定是:当您修复一个包含 n 个元素且 n > 0 的数组时,您将获得一个指向缓冲区的指针,您可以从中读取和写入 n 个元素。

现在,当 n 为零时,null 是 一个指向缓冲区的指针,您可以从中读取和写入零个元素,因此事实证明,在 n 的情况下实际上满足了该约定为零。 C# 语言不需要这样做。规范说

如果数组表达式为空或数组有零个元素,则固定语句的行为由实现定义。

因此,实现将完全在其权利范围内,例如,在您的程序中引发异常。 C#语言规范实际上根本没有定义你的程序的含义。

您试图在标签外使用fixed 来做一些极其危险和错误的事情。不要那样做。您应该在数组上使用fixed 仅用于一件事:获取指向可以读写的 n 个元素的缓冲区的指针。

有没有办法在代码中获取空数组的实际地址?

是的。使用GCHandle 手动固定。

固定托管对象以获取其地址几乎总是危险和错误的。

我需要它才能访问数组的标题,以便即时修改数组的运行时类型。

这总是危险和错误的。在任何情况下都不要这样做。

【讨论】:

  • +1 你说了那么多危险和错误
  • 只是出于好奇,上述合同是否有任何 CIL/CLR 参考资料?我很想详细了解 CLR 如何处理指针。
  • @MikeChristensen:我只是想澄清一下 OP 在这里所做的事情是危险和错误的
  • @Romoku:实际上情况比我在文字中暗示的要糟糕得多,我会解决这个问题。我建议您参考 C# 4.0 规范第 18.6 节,其中指出“如果数组表达式为空或数组有零个元素,则固定语句的行为是实现定义的。”
  • @Romoku 谷歌搜索规范中的引用,我在 MSDN 上找到了 the docs on "fixed statement"
【解决方案2】:

获取地址的方法是发GCHandle

GCHandle to get address(pointer) of .net object

GCHandle handle;
IntPtr ptr;
byte[] bytes;

bytes = new byte[1];
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

ptr = handle.AddrOfPinnedObject();
ptr.ToInt32().Dump(); // Prints 239580124

handle.Free();

unsafe {
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 239580124
    }
}

bytes = new byte[0];
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

ptr = handle.AddrOfPinnedObject();
ptr.ToInt32().Dump(); // Prints 239609660

handle.Free();

unsafe {
    fixed(void* pBytes = bytes)
    {
        ((int)pBytes).Dump(); //prints 0
    }
}

请参阅 Eric Lippert's answer 了解它为何如此运作。

【讨论】:

  • +!有趣的。这回答了如何,而不是why(关于空指针)。看来void* pBytes = bytes 的赋值操作不仅仅是哑指针算术;它涉及一些逻辑。
  • 我不知道 CLR 如何处理指针的语义,所以这将留给有更多知识的人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多