9

Question

Is the value of a C# decimal stored on the heap when it is a local variable?

What I (believe to) know

  • The decimal struct has 16 bytes.
  • The value of a local variable of type ValueType is stored on the stack, and decimal inherits from ValueType.
  • On a 32bit system, the reference memory space is only 4 bytes big; with reference memory space I mean the boxes below labelled for example baz or bar enter image description here

    Image source.

  • For ValueTypes, that's where the value is stored; for reference types, that's where either null or the reference to the heap memory location is stored.

How can the value of a decimal, which is 16 bytes big, be stored on the stack at all where there are only 4 bytes available on a 32bit system?

Where did my thinking go wrong?

What I have read

Community
  • 1
  • 1
Lernkurve
  • 20,203
  • 28
  • 86
  • 118
  • "On a 32bit system, the memory space is only 4 bytes big." Can you tell more? – Matteo Umili Oct 20 '15 at 13:07
  • @codroipo: Let me research... – Lernkurve Oct 20 '15 at 13:08
  • 1
    A stack variable can happily be bigger than 4 bytes. I think you have misunderstood in thinking it's limited to 4 bytes on a 32 bit system. – Baldrick Oct 20 '15 at 13:08
  • What is your source for the assertion that the stack size is 4bytes? cf. http://stackoverflow.com/questions/823724/stack-capacity-in-c-sharp – Matt Oct 20 '15 at 13:09
  • 1
    Quote: On a 32bit system, the memory space is only 4 bytes big. What memory space are you referring to??? I would say: on a 32bit system, the dimension of a pointer is 4bytes. i.e. you can address only 2^32 different memory locations. – Gian Paolo Oct 20 '15 at 13:09
  • @Baldrick I think that he read that a reference variable's size in 32-bit environment is 4 bytes – Matteo Umili Oct 20 '15 at 13:10
  • @codroipo: Yup, that's what I read. I've added an illustration above. – Lernkurve Oct 20 '15 at 13:14
  • 3
    A *reference* living on the stack is 4 bytes on a 32 bit system. That is what your illustration shows. However, a *value type* on the stack can be almost any size. – Baldrick Oct 20 '15 at 13:14
  • @Baldrick: Yes, I meant the _reference_ size (I've updated the bullet in the question above). I don't understand the _value type_ part. Can you elaborate or illustrate? – Lernkurve Oct 20 '15 at 13:15
  • http://www.yoda.arachsys.com/csharp/memory.html – Tom Oct 20 '15 at 13:18
  • An object on the stack can be *either* a 4-byte reference to an object of any size on the heap, *or* a value-type (struct or primitive) object in its own right of any size. An example would be a `DateTime` or `Decimal`. These are value types, and are both larger than 4 bytes. And they can exist on the stack as objects in their own right (no references involved). – Baldrick Oct 20 '15 at 13:18
  • The stack can contain thousands of values, no matter if the system is 32-bit or 64-bit. If we look at the [source for `decimal`](http://referencesource.microsoft.com/#mscorlib/system/decimal.cs), we see four instance fields in the struct: `private int flags; private int hi; private int lo; private int mid;` So unless the CLR uses special ad-hoc tricks for decimal, we would expect that each `decimal` on the stack consisted of four `Int32` values next to each other (so to speak). – Jeppe Stig Nielsen Oct 20 '15 at 13:19
  • Note that not all local variables are stored on the heap. Variables in async methods, iterator blocks, and closed over variables are hoisted to be fields of a class. – Servy Oct 20 '15 at 13:22

1 Answers1

17

How can the value of a decimal, which is 16 bytes big, be stored on the stack at all where there are only 4 bytes available on a 32bit system? Where did my thinking go wrong?

Your thinking went wrong somewhere along the lines of "a 32 bit reference fits into 4 bytes" and concluding from that "the stack is 4 bytes long". The stack is by default a million bytes long; if the runtime decides to put a decimal variable (or value) on the stack then it reserves 16 of those million bytes.

It is good that you are thinking about how computers work under the hood, but of course do not forget that the C# team has worked hard to make a language where you don't have to care whether variables are on the stack or the heap. You have to be writing some very advanced programs for it to matter; I have never written a line-of-business C# program where I needed to worry about stack vs heap and I've been writing C# programs for 10+ years.

UPDATE:

I've updated the image to illustrate that I don't think the entire stack was 4 bytes long but only the one reference "memory box".

OK, great. So again, let's suppose a reference is four bytes, which on some machines it is. If the runtime needs to put a reference on the stack, it reserves four bytes on the stack. There is no requirement that the stack be reserved in four-byte chunks. It can be reserved in chunks of any size.

So I will update my explanation. You went wrong when you assumed "a reference is four bytes, therefore the stack can only be reserved in four-byte chunks". Any number of bytes can be reserved on the stack, from zero up to a million.

(Please don't try to reserve a million; bad things will happen. There's a reason why the .NET guidelines say to make structs that are 16 bytes or less!)

Now, for performance reasons on some architectures the runtime might attempt to reserve stack memory so that each chunk is aligned to four-byte boundaries. Some hardware is really slow if data is misaligned. But this is a level of detail that you almost never need to worry about in C#.

UPDATE again:

Here's another way to think about it. Suppose we have two normal local variables that are 4-byte ints:

int x;
int y;

That gets two chunks of memory that are four bytes each. Now suppose we have a struct:

struct S 
{ 
    public int x; 
    public int y; 
}

and we make a local variable:

S s;

That is exactly the same as before. We have two variables on the stack, and those variables are named s.x and s.y. Putting a struct on the stack is neither more nor less than putting all the field variables that are in the struct on the stack.

YET ANOTHER UPDATE:

Another thing that might be confusing you is that there is another kind of temporary storage called "registers" that are also used to store local variables, and registers really genuinely are only four bytes long. (Except for the ones which are 8, but let's not go there.) How is a 16 byte struct stored in a register? It isn't. The runtime simply doesn't put big structs into registers.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I've updated the image to illustrate that I don't think the entire stack was 4 bytes long but only the one _reference_ "memory box". – Lernkurve Oct 20 '15 at 13:26
  • 1
    @Lernkurve The point is that the stack isn't made up of 32-bit chunks, it's made up of 8-bit chunks (named bytes) and the runtime is free to use as many of those as it needs to fit a value type. Just because reference types use 4 of those on a 32-bit system doesn't mean nothing can use more than that. – Kyle Oct 20 '15 at 13:34
  • @Kyle: Thanks! That was the missing piece of information (which has now also been updated in this answer). – Lernkurve Oct 20 '15 at 13:38
  • @Lernkurve: I've added a few updates to address (ha ha) your updated question. – Eric Lippert Oct 20 '15 at 13:42
  • @EricLippert Does C#'s padding of structs come into play here? I know that structs can be padded to some extent, but I'm not intimately familiar with the rules. How does that affect what possible sizes a structure can be and how mal-aligned from word boundaries its allowed to be? – Servy Oct 20 '15 at 13:47
  • @Servy: It is a mark of how little interop programming I have done this past decade that I no longer remember what the default struct padding rules are. The last time I wrote any serious interop code was to write a little library that talked directly to the Office COM IStorage objects and I knew what the struct packing rules were then, I'm sure, but its 12 years later and I'd have to look it up again. – Eric Lippert Oct 20 '15 at 13:49