Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: unsafe pointer to empty array is null?

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.

like image 253
Cristian Diaconescu Avatar asked Dec 11 '22 14:12

Cristian Diaconescu


2 Answers

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

That's a strange question. Why do you believe it should?

The contract is: when you fix an array with n elements where n > 0 you get a pointer to a buffer from which you can read and write n elements.

Now, When n is zero, null is a pointer to a buffer from which you can read and write zero elements, so as it turns out, that contract is actually met for the case where n is zero. The C# language is not required to do so. The specification says

The behavior of the fixed statement is implementation-defined if the array expression is null or if the array has zero elements.

So the implementation would be entirely within its rights to, say, throw an exception in your program. The meaning of your program is actually not defined at all by the C# language specification.

You're trying to use fixed off-label in order to do something incredibly dangerous and wrong. Don't do that. You should use fixed on an array for one thing only: to obtain a pointer to a buffer of n elements that you can read and write.

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

Yes. Pin manually with a GCHandle.

Pinning a managed object in order to obtain its address is almost always dangerous and wrong.

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

That's always dangerous and wrong. Don't do that under any circumstances.

like image 101
Eric Lippert Avatar answered Mar 01 '23 18:03

Eric Lippert


The way to get the address is to make a GCHandle.

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

See Eric Lippert's answer for why it works like this.

like image 21
Dustin Kingen Avatar answered Mar 01 '23 18:03

Dustin Kingen