Stack Implementation Details - Part One
- Transfer

Some time ago I wrote that “links” are not “addresses” when it comes to C # and the placement of its objects in memory. Although this is true, it is just an implementation detail, but not the meaning of a “link”. Another implementation detail that is often confused with the gist is that "memory for value types is allocated on the stack." I often see this, because that's what it says in our documentation .
Almost every article I see describes in detail (often incorrectly) what the stack is and that the main difference between meaningful and reference types is that meaningful types are located on the stack. I am sure you can find many examples of such articles on the net.
I believe that the definition of significant types, which is based on implementation details, and not on their observed behavior, is both confusing and not entirely correct. The most significant characteristic of an object of a significant type is not how it is located in memory, but how they behave in terms of semantics: “objects of significant types” are always transmitted “by value”, i.e. are copied. If the main differences between reference and meaningful types were in the location details in memory, we would call them “types on the heap” and “types on the stack”. But in the general case, this has nothing to do with the essence. In the general case, it is important how the instances of significant types are copied and compared.
Unfortunately, the documentation is not focused on the most significant features, but focused on implementation details and misses the point of the significant types. I would very much like for all those articles that explain “what is the stack” instead to explain what “copy by value” is and how a misunderstanding of this mechanism can cause errors.
The assertion that meaningful types are stacked is generally not true. The MSDN documentation correctly noted that meaningful types are sometimes stacked on the stack. For example, a field of type int in a reference type is part of an object of this type and, like the whole object, its field is located on the heap. The same story with local variables that fall into the closure of anonymous methods (*), because they essentially become fields of the hidden class and are also located on the heap, so local variables can be located on the heap even if they are of a significant type.
In short, we have an explanation that does not explain anything. Discarding performance considerations, what else, in terms of developer expectations, can cause CLRjitter to place an int variable on the stack rather than on the heap? Nothing, until the specification is violated, the system can choose the most effective code generation strategy.
Yeah, no one promised that the operating system on top of which the CLI is implemented provides an array of 1 megabyte in size called the "stack". Windows usually does this and this one megabyte array is a great place to efficiently store small objects with a short lifetime, but there are no requirements or guarantees that the operating system provides this kind of structure or that jitter will use it. In principle, Jitter may decide to create all local variables on the heap despite the loss of performance, this will work as long as the semantic requirements for meaningful types are satisfied.
It’s even worse to think that the meaningful types are “fast and small” and the reference types are “big and slow”. Indeed, significant types can be generated by the jitter into code on the stack, which is very fast both when allocating and when clearing memory. But at the same time, large structures created on the heap, such as an array of elements of a significant type, are also created very quickly, provided that they are initialized with default values. And reference types take up extra memory space. And of course, there are conditions when significant types give a big gain in performance. But in the vast majority of programs, how local variables are created and destroyed cannot be a bottleneck in terms of performance.
Nano optimizations for converting reference types into meaningful winnings of several nanoseconds are not worth it. This should only be done if the profiler data has shown that there are real problems that can be solved by using meaningful types instead of reference types. I do not have such data. When choosing to use a reference or meaningful type, I am always guided by how the type that I create must behave semantically.
(*) It is true for an iterator block.
From a translator: recently, I again had the opportunity to conduct several interviews and often to the question “what are meaningful and reference types” I heard “meaningful types are types whose instances are located on the stack, and reference types are types whose instances are located in heap. " So here is a translation of a very old article by Eric Lippert, which has not lost its relevance. I tried to make the translation as readable and easy to read in Russian as possible, so that it differs significantly from the original in form, but not in meaning.