C#:指向空数组的不安全指针是否为空?

时间:2013-06-13 15:36:29

标签: c# pointers unsafe

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,我实际上需要能够到达数组的头部,以便动态修改数组的运行时类型。

2 个答案:

答案 0 :(得分:12)

  

为什么固定的非托管指针不一样?

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

合同是:当您修复一个包含n个元素的数组时,其中n&gt; 0你得到一个指向缓冲区的指针,你可以从中读取和写入n个元素。

现在,当n为零时,null是指向缓冲区的指针,您可以从中读取和写入零元素,因此事实证明,在n的情况下实际符合该合同是零。 不要求使用C#语言。规范说明

  

如果数组表达式为null或者数组的元素为零,则fixed语句的行为是实现定义的。

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

你正在尝试使用fixed标签来做一些非常危险和错误的事情。不要那样做。你应该只在一个数组上使用fixed来获取一个东西:获取一个指向你可以读写的n个元素的缓冲区的指针。

  

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

是。使用GCHandle手动固定。

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

  

我需要能够到达数组的头,以便动态修改数组的运行时类型。

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

答案 1 :(得分:1)

获取地址的方法是制作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了解其原因。