4

In an unsafe block, I'm trying to get a pointer to a byte array. But I get different results depending on the declared size of the array:

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 ?!
    }
}

If I open the immediate window and type &bytes, I get the actual addresses of the byte arrays, including the case with the empty array.

Why doesn't the fixed unmanaged pointer work the same?

UPDATE:

Here's the same code and what I get from the immediate window:

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.
    }
}

The 8-byte difference between the address of the array object (&bytes) and the array pointer is explained by the object's header.

The array representation in memory is:

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

The unsafe pointer actually points to the start of, well, actual data (i.e. what would be marshalled to an unmanaged context)

Is there a way I could get, in code, the actual address of the empty array?

FWIW, I actually need that to be able to get to the array's header, to modify the array's runtime-type on the fly.

4

2 回答 2

12

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

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

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

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

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

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

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

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

是的。使用 手动固定GCHandle

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

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

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

于 2013-06-13T16:03:38.870 回答
1

获取地址的方法是制作一个GCHandle.

请参阅GCHandle 以获取 .net 对象的地址(指针)

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 的回答,了解它为何如此工作。

于 2013-06-13T15:55:11.950 回答