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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With