I'm refactoring my libraries to use Span<T>
for avoiding heap allocations if possible but as I target also older frameworks I'm implementing some general fallback solutions as well. But now I found a weird issue and I'm not quite sure whether I found a bug in .NET Core 3 or am I doing something illegal.
The issue:
// This returns 1 as expected but cannot be used in older frameworks: private static uint ReinterpretNew() { Span<byte> bytes = stackalloc byte[4]; bytes[0] = 1; // FillBytes(bytes); // returning bytes as uint: return Unsafe.As<byte, uint>(ref bytes.GetPinnableReference()); } // This returns garbage in .NET Core 3.0 with release build: private static unsafe uint ReinterpretOld() { byte* bytes = stackalloc byte[4]; bytes[0] = 1; // FillBytes(bytes); // returning bytes as uint: return *(uint*)bytes; }
Interestingly enough, ReinterpretOld
works well in .NET Framework and in .NET Core 2.0 (so I could be happy with it after all), still, it bothers me a bit.
Btw. ReinterpretOld
can be fixed also in .NET Core 3.0 by a small modification:
//return *(uint*)bytes; uint* asUint = (uint*)bytes; return *asUint;
My Question:
Is this a bug or does ReinterpretOld
work in older frameworks only by accident and should I apply the fix also for them?
Remarks:
[MethodImpl(MethodImplOptions.NoInlining)]
to ReinterpretOld
but it had no effect.To set the unsafe option in Visual Studio 2012, click on Project in the main menu, select the Build pane, and check the box that says "allow unsafe code."
Unsafe code in C# isn't necessarily dangerous; it's just code whose safety cannot be verified. Unsafe code has the following properties: Methods, types, and code blocks can be defined as unsafe. In some cases, unsafe code may increase an application's performance by removing array bounds checks.
This is responsible for things like memory management and garbage collection. So unmanaged simply runs outside of the context of the CLR. unsafe is kind of "in between" managed and unmanaged. unsafe still runs under the CLR, but it will let you access memory directly through pointers.
What Does Unsafe Mean? Unsafe is a C programming language (C#) keyword used to denote a section of code that is not managed by the Common Language Runtime (CLR) of the . NET Framework, or unmanaged code.
Ooh, this is a fun find; what is happening here is that your local is getting optimized away - there are no locals remaining, which means that there is no .locals init
, which means that stackalloc
behaves differently, and does not wipe the space;
private static unsafe uint Reinterpret1() { byte* bytes = stackalloc byte[4]; bytes[0] = 1; return *(uint*)bytes; } private static unsafe uint Reinterpret2() { byte* bytes = stackalloc byte[4]; bytes[0] = 1; uint* asUint = (uint*)bytes; return *asUint; }
becomes:
.method private hidebysig static uint32 Reinterpret1() cil managed { .maxstack 8 L_0000: ldc.i4.4 L_0001: conv.u L_0002: localloc L_0004: dup L_0005: ldc.i4.1 L_0006: stind.i1 L_0007: ldind.u4 L_0008: ret } .method private hidebysig static uint32 Reinterpret2() cil managed { .maxstack 3 .locals init ( [0] uint32* numPtr) L_0000: ldc.i4.4 L_0001: conv.u L_0002: localloc L_0004: dup L_0005: ldc.i4.1 L_0006: stind.i1 L_0007: stloc.0 L_0008: ldloc.0 L_0009: ldind.u4 L_000a: ret }
I think I'd be happy to say that this is a compiler bug, or at least: an undesirable side-effect and behavior given that previous decisions have been put in place to say "emit the .locals init", specifically to try and keep stackalloc
sane - but whether the compiler folks agree is up to them.
The workaround is: treat the stackalloc
space as undefined (which, to be fair, is what you're meant to do); if you expect it to be zeros: manually zero it.
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