-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spec: Define fixed behavior with null/empty arrays #6707
Comments
👍 |
Agreed, and perhaps cleaning up other implementation-defined behaviors is warranted as well. |
Is it a good idea to make the pointer zero for an empty array? This does seem like a pointless special case. Does it serve any purpose or is it for compat only? |
@GSPP: I guess the reason is that zero-sized arrays naturally map to In fact, I have often relied on this behavior, not knowing that it is undefined. |
There's definitely the compat issue, as folks have relied on this behavior. But it's actually useful behavior... not necessarily the part about the address being null, but at least that it's valid to use an empty array with fixed and that you get some address (and as long as you're getting some address, null is a good choice). Code along the lines of the following (I'm simplifying) is very common: [DllImport("SomeNativeLibrary")]
private static extern unsafe void SomeFunction(byte* bytes, int bytesLength);
...
unsafe void UseSomeFunction(byte[] bytes)
{
fixed (byte* ptr = bytes)
{
SomeFunction(ptr, bytes.Length);
}
} Given an arbitrary bytes array, with how fixed is defined in C# today, this is invalid code. Instead developers end up writing code like the following (if they even realize the above is incorrect): unsafe void UseSomeFunction(byte[] bytes)
{
if (bytes.Length > 0)
{
fixed (byte* ptr = bytes)
{
SomeFunction(ptr, bytes.Length);
}
}
else
{
SomeFunction(null, 0);
}
} which often ends up duplicating much more code than I have here, or: unsafe void UseSomeFunction(byte[] bytes)
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
IntPtr ptr = handle.AddressOfPinnedObject();
SomeFunction((byte*)ptr, bytes.Length);
}
finally
{
handle.Free();
}
} which is both more complicated and measureably more expensive, or: private static readonly byte[] s_nonEmptyArray = new byte[1];
unsafe void UseSomeFunction(byte[] bytes)
{
byte[] tmp = bytes.Length > 0 ? bytes : s_nonEmptyArray;
fixed (byte* ptr = tmp)
{
SomeFunction(ptr, bytes.Length);
}
} which is similarly more complicated with overheads. Etc. All of that can be avoided with the C# compiler doing what it already does. |
@stephentoub yeah, much better to define it. The undefinedness is pointless in this case. I just realize now that it would be hard for the compiler to obtain some address from the array in case it is zero length because ldelema throws in that case. I guess it could use |
👍 I agree it's much better to have defined behavior here. Especially since all the compilers already handle this case in the same fashion. |
This is already implemented; we just need to update the spec ;) |
The C# spec section 18.6 about the fixed statement has the following sentence:
I propose we make this behavior well-defined rather than leaving it up to the implementation.
Both the previous native C# compiler and Roslyn set the pointer to null for a null or empty array, e.g. with Roslyn this program prints out 0:
with IL that does the check for null/empty explicitly:
The Mono mcs compiler looks to have the same behavior, using a null pointer for a null or empty array:
However, because the spec states that the behavior isn't well-defined, we can't rely on it in code we write, and as such there are many instances of code actively defending against the possibility, doing a variety of awkward workarounds, e.g.
GCHandle.Alloc(arr, GCHandleType.Pinned)
withAddressOfPinnedObject
.All of these lead to more expensive and more complicated code, none of which is necessary due to the compiler(s) already doing the right thing.
The spec language currently reads:
I propose we change it to:
cc: @MadsTorgersen, @jaredpar
The text was updated successfully, but these errors were encountered: