Problem
When you create an instance of a class with the new operator, memory gets allocated on the heap. When you create an instance of a struct with the new operator where does the memory get allocated, on the heap or on the stack ?
Asked by kedar kamthe
Solution #1
Let me see if I can clarify this up for you.
To begin with, Ash is correct: the question is not about allocating value type variables. That’s a separate question, with a different response than “on the stack.” It’s more complicated than that (and C# 2 makes it even more so). I have an article on the subject and will expand on it if asked, but for now, let’s concentrate on the new operator.
Second, it all depends on the level at which you’re discussing. In terms of the IL it generates, I’m looking at what the compiler does with the source code. It’s more than possible that the JIT compiler will do clever things in terms of optimising away quite a lot of “logical” allocation.
Third, I’m avoiding generics, primarily because I don’t know the solution and, in part, because it would overcomplicate things.
Finally, this is all based on the present implementation. The C# spec doesn’t go into great depth about this; it’s essentially an implementation detail. Some people argue that managed code developers shouldn’t give a damn. I’m not sure I’d go that far, but it’s worth picturing a scenario where all local variables are stored on the heap and the standard is still followed.
You can call a parameterless constructor (e.g. new Guid()) or a parameterful constructor (e.g. new Guid(someString)) with the new operator on value types. These produce IL that is vastly different. To explain why, consider the C# and CLI specifications: according to C#, all value types have a parameterless constructor. According to the CLI spec, no value types have parameterless constructors. (Fetch the constructors of a value type with reflection some time – you won’t find a parameterless one.)
Because it keeps the language constant, C# treats “initialize a value with zeroes” as a constructor. Because there is no real code to call – and certainly no type-specific code – it makes sense for the CLI to think of it differently.
It also matters what you’re going to do with the value once it’s been initialized. The IL that was utilized for
Guid localVariable = new Guid(someString);
is not the same as the IL used for:
myInstanceOrStaticVariable = new Guid(someString);
Furthermore, if the value is utilized as an intermediary value, such as an argument to a method call, the situation changes slightly. Here’s a quick test program to demonstrate all of these differences. It doesn’t distinguish between static and instance variables: the IL would be different between stfld and stsfld, but that’s it.
using System;
public class Test
{
static Guid field;
static void Main() {}
static void MethodTakingGuid(Guid guid) {}
static void ParameterisedCtorAssignToField()
{
field = new Guid("");
}
static void ParameterisedCtorAssignToLocal()
{
Guid local = new Guid("");
// Force the value to be used
local.ToString();
}
static void ParameterisedCtorCallMethod()
{
MethodTakingGuid(new Guid(""));
}
static void ParameterlessCtorAssignToField()
{
field = new Guid();
}
static void ParameterlessCtorAssignToLocal()
{
Guid local = new Guid();
// Force the value to be used
local.ToString();
}
static void ParameterlessCtorCallMethod()
{
MethodTakingGuid(new Guid());
}
}
Here’s the IL for the class, excluding irrelevant bits (such as nops):
.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
// Removed Test's constructor, Main, and MethodTakingGuid.
.method private hidebysig static void ParameterisedCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
L_0010: ret
}
.method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
{
.maxstack 2
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: ldstr ""
L_0008: call instance void [mscorlib]System.Guid::.ctor(string)
// Removed ToString() call
L_001c: ret
}
.method private hidebysig static void ParameterisedCtorCallMethod() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0011: ret
}
.method private hidebysig static void ParameterlessCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
L_0006: initobj [mscorlib]System.Guid
L_000c: ret
}
.method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
// Removed ToString() call
L_0017: ret
}
.method private hidebysig static void ParameterlessCtorCallMethod() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
L_0009: ldloc.0
L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0010: ret
}
.field private static valuetype [mscorlib]System.Guid field
}
As you can see, there are lots of different instructions used for calling the constructor:
I hope this demonstrates how complex the subject is while also shedding some light on it. Every call to new allocates space on the stack in certain conceptual meanings, but as we’ve shown, this isn’t the case even at the IL level. I’d like to focus on one situation in particular. Consider the following method:
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
That “logically” has four stack allocations – one for the variable and one for each of the three new calls – but the stack is only allocated once (for that specific function), and then the same storage location is reused.
EDIT: To be clear, this is only true in some instances… for example, if the Guid constructor throws an exception, the value of guid will be hidden, which is why the C# compiler can reuse the same stack slot. For further information and an example of when it doesn’t apply, see Eric Lippert’s blog post on value type construction.
I’ve learnt a lot while composing this response; if anything is unclear, please ask for explanation!
Answered by Jon Skeet
Solution #2
Depending on the conditions, the memory containing a struct’s fields can be allocated on the stack or the heap. It will be allocated on the stack if the struct-type variable is a local variable or argument that is not captured by any anonymous delegate or iterator class. If the variable is part of a class, it will be allocated on the heap within that class.
If the struct is allocated on the heap, it is not essential to use the new operator to allocate memory. The constructor’s sole purpose would be to set the field values according to whatever is in it. If the constructor isn’t called, the default values will be applied to all fields (0 or null).
Similarly, you must use either a custom constructor or the default constructor for structs allocated on the stack, except that C# needs all local variables to be set to a value before they can be used, so you must call either a custom constructor or the default constructor (a constructor that takes no parameters is always available for structs).
Answered by Jeffrey L Whitledge
Solution #3
To put it compactly, new is a misnomer for structs, calling new simply calls the constructor. The only storage location for the struct is the location it is defined.
If it is a member variable it is stored directly in whatever it is defined in, if it is a local variable or parameter it is stored on the stack.
In contrast, classes hold a reference to wherever the complete struct would have been stored, whereas the reference links to somewhere on the heap. (Member within, stack local/parameter)
It could be useful to take a look at C++, where there is no clear distinction between class and struct. (There are other words in the language with comparable meanings, but they exclusively refer to items’ default accessibility.) When you call new, you obtain a pointer to the heap location, whereas non-pointer references are saved directly on the stack or within the other object, similar to how structs work in C#.
Answered by Guvante
Solution #4
Structs, like other value types, always go where they’re defined.
For more information on when to utilize structs, see this question. For additional information on structs, see this question.
Edit: I had incorrectly stated that they are ALWAYS placed in the stack. This isn’t true.
Answered by Esteban Araya
Solution #5
I’m sure I’m missing something, but why are we concerned with allocation?
Because value types are passed by value, they can’t be altered outside of the scope in which they’re declared. The [ref] keyword must be added to be able to change the value.
Reference types can be altered and are passed by reference.
Immutable reference types are the most common, with strings being the most frequent.
Value types -> zero memory array layout/initialization [name,zip] [name,zip] Null -> reference types -> zero memory [ref] [ref]
Answered by user18579
Post is based on https://stackoverflow.com/questions/203695/does-using-new-on-a-struct-allocate-it-on-the-heap-or-stack