Yes, the spec says it is undefined, but the compiler emits localloc
CIL intruction for stackalloc
. And this is what ECMA Specs says about localloc
:
The localloc instruction allocates size (type native unsigned int)
bytes from the local dynamic memory pool and returns the address (a
managed pointer, type &) of the first allocated byte. The block of
memory returned is initialized to 0 only if the initialize flag on the
method is true (see Partition I). The area of memory is newly
allocated. When the current method returns the local memory pool is
available for reuse.
The initialize flag, also known as localsinit
flag, is emitted for every method by the compiler because it is required for verifiable code.
Please look at this issue on coreclr asking to stop zeroing memory on stackalloc. At the end of the issue jkotas says:
The current plan is:
C# will keep zero initializing by default. Changing the default would
be too breaking. We have a set of issues opened to make the zero
initialization done by the JIT more efficient or reduce need for it
(#13827, #13823, #13825) Folks who really want to get the last bit of
performance by avoiding zero initialization can use custom ILLinker
step (mono/linker#159) when they know what they are doing. We do this
for CoreLib today (via VM hack, but we should switch to the ILLinker),
and we plan to experiment with this in CoreFX (dotnet/corefx#25956).
Based on the results of these experiments, we may consider introducing
a more streamlined way to do this in future. @ahsonkhan You should
consider experimenting with it in CoreFXLab as well if you believe
that it would help.
And see this csharplang proposal
So the conclusion is: the memory is initialized to zero in practice
However there is a bug/feature in the compiler that prevents from emiting localsinit
flag. Unsafe methods that don't declare other variables and use the stack allocated variable only to passing it to other method, don't get marked with the localsinit
flag.
Here is an example of such bug/feature:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace InformalTests
{
class Program
{
const int n = 100_000_000;
static unsafe void Main(string[] args)
{
var watch = Stopwatch.StartNew();
for (int i =0; i < n; i++)
{
ThisMethodDoes_NOT_InitializeStackAllocatedMemory();
}
watch.Stop();
Console.WriteLine($"NOT INITIALIZED elapsed time {watch.Elapsed}");
watch.Restart();
for (int i = 0; i < n; i++)
{
ThisMethodInitializeStackAllocatedMemory();
}
watch.Stop();
Console.WriteLine($"INITIALIZED Elapsed time {watch.Elapsed}");
}
private static unsafe string ThisMethodDoes_NOT_InitializeStackAllocatedMemory()
{
// avoid declaring other local vars, or doing work with stackalloc
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
char* pointer = stackalloc char[256];
return CreateString(pointer, 256);
}
private static unsafe string ThisMethodInitializeStackAllocatedMemory()
{
//Declaring a variable other than the stackallocated, causes
//compiler to emit .locals int cil flag, so it's slower
int i = 256;
char* pointer = stackalloc char[256];
return CreateString(pointer, i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe string CreateString(char* pointer, int length)
{
return "";
}
}
}
The not initialized method is five times faster than the initialized one.