堆和栈 原文

http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx?ArticleID=9adb0e3c-b3f6-40b5-98b5-413b6d348b91&PagePath=/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx

Even though with the .NET framework we don‘t have to actively worry about memory management and garbage collection (GC), we still have to keep memory management and GC in mind in order to optimize the performance of our applications. Also, having a basic understanding of how memory management works will help explain the behavior of the variables we work with in every program we write.  In this article I‘ll cover the basics of the Stack and Heap, types of variables and why some variables work as they do.

There are two places the .NET framework stores items in memory as your code executes.  If you are not yet familiar with them, let me introduce you to the Stack and the Heap. Both the Stack and Heap help us run our code. They reside in the operating memory on our machine and contain the pieces of information we need to make it all happen.

Stack vs. Heap: What‘s the difference?

The Stack is more or less responsible for keeping track of what‘s executing in our code (or what‘s been "called").  The Heap is more or less responsible for keeping track of our objects (our data, well... most of it; we‘ll get to that later).

Think of the Stack as a series of boxes stacked one on top of the next.  We keep track of what‘s going on in our application by stacking another box on top every time we call a method (called a Frame).  We can only use what‘s in the top box on the Stack.  When we‘re done with the top box (the method is done executing) we throw it away and proceed to use the stuff in the previous box on the top of the Stack. The Heap is similar except that its purpose is to hold information (not keep track of execution most of the time) so anything in our Heap can be accessed at  any time.  With the Heap, there are no constraints as to what can be accessed like in the Stack.  The Heap is like the heap of clean laundry on our bed that we have not taken the time to put away yet; we can grab what we need quickly. The Stack is like the Stack of shoe boxes in the closet where we have to take off the top one to get to the one underneath it.

The picture above, while not really a true representation of what‘s happening in memory, helps us distinguish a Stack from a Heap.
 
The Stack is self-maintaining, meaning that it basically takes care of its own memory management.  When the top box is no longer used, it‘s thrown out.  The Heap, on the other hand, must worry about Garbage collection (GC), which deals with how to keep the Heap clean (no one wants dirty laundry laying around, it stinks!).

What goes on the Stack and Heap?

We have four main types of things we‘ll be putting in the Stack and Heap as our code is executing: Value Types, Reference Types, Pointers, and Instructions.

Value Types:

In C#, all the "things" declared with the following list of type declarations are Value types (because they are from System.ValueType):

  • bool
  • byte
  • char
  • decimal
  • double
  • enum
  • float
  • int
  • long
  • sbyte
  • short
  • struct
  • uint
  • ulong
  • ushort

Reference Types:

All the "things" declared with the types in this list are Reference types (and inherit from System.Object, except, of course, for object which is the System.Object object):

  • class
  • interface
  • delegate
  • object
  • string

Pointers:

The third type of "thing" to be put in our memory management scheme is a Reference to a Type. A Reference is often referred to as a Pointer. We don‘t explicitly use Pointers, they are managed by the Common Language Runtime (CLR). A Pointer (or Reference) is different than a Reference Type in that when we say something is a Reference Type, it means we access it through a Pointer. A Pointer is a chunk of space in memory that points to another space in memory.  A Pointer takes up space just like any other thing that we‘re putting in the Stack and Heap and its value is either a memory address or null.

Instructions:

You‘ll see how the  "Instructions" work later in this article...

How is it decided what goes where? (Huh?)

Ok, one last thing and we‘ll get to the fun stuff.

Here are our two golden rules:

  1. A Reference Type always goes on the Heap; easy enough, right?
  2. Value Types and Pointers always go where they were declared. This is a little more complex and needs a bit more understanding of how the Stack works to figure out where "things" are declared.

The Stack, as we mentioned earlier, is responsible for keeping track of where each thread is during the execution of our code (or what‘s been called).  You can think of it as a thread "state" and each thread has its own Stack.  When our code makes a call to execute a method the thread starts executing the instructions that have been JIT compiled and live on the method table, it also puts  the method‘s parameters on the thread Stack. Then, as we go through the code and run into variables within the method, they are placed on top of the Stack. This will be easiest to understand with an example.

Take the following method:

public int AddFive(int pValue)
          {

int result;
                result = pValue + 5;
                return result;
          }

Here‘s what happens at the very top of the Stack.  Keep in mind that what we are looking at is placed on top of many other items already living in the Stack:

Once we start executing the method, the method‘s parameters are placed on the Stack (we‘ll talk more about passing parameters later).

NOTE : the method does not live on the stack and is illustrated just for reference.

Next, control (the thread executing the method) is passed to the instructions to the AddFive() method which lives in our type‘s method table, a JIT compilation is performed if this is the first time we are hitting the method.

As the method executes, we need some memory for the "result" variable and it is allocated on the Stack.

The method finishes execution and our result is returned.

And all memory allocated on the Stack is cleaned up by moving a pointer to the available memory address where AddFive() started and we go down to the previous method on the stack (not seen here).

In this example, our "result" variable is placed on the stack.  As a matter of fact, every time a Value Type is declared within the body of a method, it will be placed on the stack.

Now, Value Types are also sometimes placed on the Heap.  Remember the rule, Value Types always go where they were declared?  Well, if a Value Type is declared outside of a method, but inside a Reference Type then it will be placed within the Reference Type on the Heap.

Here‘s another example.

If we have the following MyInt class (which is a Reference Type because it is a class):

public class MyInt
          {

publicint MyValue;
          }

and the following method is executing:

public MyInt AddFive(int pValue)
          {
                MyInt result =

new MyInt();
                result.MyValue = pValue + 5;
                return result;
          }

Then just as before, the thread starts executing the method and its parameters are placed on sthe thread‘s stack.

Now is when it gets interesting.

Because MyInt is a Reference Type, it is placed on the Heap and referenced by a Pointer on the Stack.

After AddFive() is finished executing (like in the first example), and we are cleaning up...

we‘re left with an orphaned MyInt in the Heap (there is no longer anyone in the Stack standing around pointing to MyInt)!

This is where the Garbage Collection (GC) comes into play.  Once our program reaches a certain memory threshold and we need more Heap space, our GC will kick off.  The GC will stop all running threads (a FULL STOP), find all objects in the Heap that are not being accessed by the main program and delete them.  The GC will then reorganize all the objects left in the Heap to make space and adjust all the Pointers to these objects in both the Stack and the Heap.  As you can imagine, this can be quite expensive in terms of performance, so now you can see why it can be important to pay attention to what‘s in the Stack and Heap when trying to write high-performance code.

Ok, that‘s great, but how does it really affect me?

Good question.

When we are using Reference Types, we‘re dealing with Pointers to the type, not the thing itself.  When we‘re using Value Types, we‘re using the thing itself.  Clear as mud, right?

Again, this is best described by example.

If we execute the following method:

public int ReturnValue()
          {

int x = new int();
                x = 3;

int y = new int();
                y = x;      
                y = 4;         
                return x;
          }

We‘ll get the value 3.  Simple enough, right?

However, if we are using the MyInt class from before:

public class MyInt

{
                publicint MyValue;
          }

and we are executing the following method:

public int ReturnValue2()
          {
                MyInt x =

new MyInt();
                x.MyValue = 3;
                MyInt y =

new MyInt();
                y = x;                 
                y.MyValue = 4;              
                return x.MyValue;
          }

What do we get?    4!

Why?...  How does x.MyValue get to be 4? Take a look at what we‘re doing and see if it makes sense:

In the first example everything goes as planned:

public int ReturnValue()
          {

int x = 3;

int y = x;    
                y = 4;
                return x;
          }

In the next example, we don‘t get "3" because both variables "x" and "y" point to the same object in the Heap.

public int ReturnValue2()
          {
                MyInt x;
                x.MyValue = 3;
                MyInt y;
                y = x;                
                y.MyValue = 4;
                return x.MyValue;
          }

Hopefully this gives you a better understanding of a basic difference between Value Type and Reference Type variables in C# and a basic understanding of what a Pointer is and when it is used.  In the next part of this series, we‘ll get further into memory management and specifically talk about method parameters.

Even though with the .NET framework we don‘t have to actively worry about memory management and garbage collection (GC), we still have to keep memory management and GC in mind in order to optimize the performance of our applications. Also, having a basic understanding of how memory management works will help explain the behavior of the variables we work with in every program we write. In this article I‘ll cover some of the behaviors we need to be aware of when passing parameters to methods.

In Part I we covered the basics of the Heap and Stack functionality and where Variable Types and Reference Types are allocated as our program executes. We also covered the basic idea of what a Pointer is.

Parameters, the Big Picture.

Here‘s the detailed view of what happens as our code executes. We covered the basics of what happens when we make a method call in Part I. Let‘s get into more detail...

When we make a method call here‘s what happens:

  1. Space is allocated for information needed for the execution of our method on the stack (called a Stack Frame). This includes the calling address (a pointer) which is basically a GOTO instruction so when the thread finishes running our method it knows where to go back to in order to continue execution.
  2. Our method parameters are copied over. This is what we want to look at more closely.
  3. Control is passed to the JIT‘ted method and the thread starts executing code. Hence, we have another method represented by a stack frame on the "call stack".

The code:

public int AddFive(int pValue)
          {
                int result;
                result = pValue + 5;
                return result;
          }

Will make the stack look like this:

NOTE : the method does not live on the stack, and is illustrated here just for reference as the beginnnig of the stack frame.
 
As discussed in Part I, Parameter placement on the stack will be handled differently depending on whether it is a value type or a reference type. A value types is copied over and the reference of a reference type is copied over.ed over.

Passing Value Types.

Here‘s the catch with value types...

First, when we are passing a value types, space is allocated and the value in our type is copied to the new space on the stack. Look at the following method:

class Class1

{

public void Go()

{

int x = 5;

AddFive(x);

Console.WriteLine(x.ToString());

}

public int AddFive(int pValue)

{

pValue += 5;

return pValue;

}

}

As the method executes, space for "x" is placed on the stack with a value of 5.

Next, AddFive() is placed on the stack with space for it‘s parameters and the value is copied, bit by bit from x.

When AddFive() has finished execution, the thread is passed back to Go() and because AddFive() has completed, pValue is essentially "removed":

So it makes sense that the output from our code is "5", right? The point is that any value type parameters passed into a method are carbon copies and we count on the original variable‘s value to be preserved.

One thing to keep in mind is that if we have a very large value type (such as a big struct) and pass it to the stack, it can get very expensive in terms of space and processor cycles to copy it over each time. The stack does not have infinite space and just like filling a glass of water from the tap, it can overflow. A struct is a value type that can get pretty big and we have to be aware of how we are handling it.

Here‘s a pretty big struct:

public struct MyStruct

{

long a, b, c, d, e, f, g, h, i, j, k, l, m;

}

Take a look at what happens when we execute Go() and get to the DoSomething() method below:

public void Go()

{

MyStruct x = new MyStruct();

DoSomething(x);

}

public void DoSomething(MyStruct pValue)

{

// DO SOMETHING HERE....

}

This can be really inefficient. Imaging if we passed the MyStruct a couple thousand times and you can understand how it could really bog things down.

So how do we get around this problem? By passing a reference to the original value type as follows:

public void Go()

{

MyStruct x = new MyStruct();

DoSomething(ref x);

}

public struct MyStruct

{

long a, b, c, d, e, f, g, h, i, j, k, l, m;

}

public void DoSomething(ref MyStruct pValue)

{

// DO SOMETHING HERE....

}

This way we end up with more memory efficient allocation of our objects in memory.

The only thing we have to watch out for when passing our value type by reference is that we have access to the value type‘s value. Whatever is changed in pValue is changed in x. Using the code below, our results are going to be "12345" because the pValue.a actually is looking at the memory space where our original x variable was declared.

public void Go()

{

MyStruct x = new MyStruct();

x.a = 5;

DoSomething(ref x);

Console.WriteLine(x.a.ToString());

}

public void DoSomething(ref MyStruct pValue)

{

pValue.a = 12345;

}

Passing Reference Types.

Passing parameters that are reference types is similar to passing value types by reference as in the previous example.

If we are using the value type

public class MyInt

{

public int MyValue;

}

And call the Go() method, the MyInt ends up on the heap because it is a reference type:

public void Go()

{

MyInt x = new MyInt();

}

If we execute Go() as in the following code ...

public void Go()

{

MyInt x = new MyInt();

x.MyValue = 2;

DoSomething(x);

Console.WriteLine(x.MyValue.ToString());

}

public void DoSomething(MyInt pValue)

{

pValue.MyValue = 12345;

}

Here‘s what happens...

  1. Starting with the call to Go() the variable x goes on the stack.
  2. Starting with the call to DoSomething() the parameter pValue goes on the stack.
  3. The value of x (the address of MyInt on the stack) is copied to pValue

So it makes sense that when we change the MyValue property of the MyInt object in the heap using pValue and we later refer to the object on the heap using x, we get the value "12345".

So here‘s where it gets interesting. What happens when we pass a reference type by reference?

Check it out. If we have a Thing class and Animal and Vegetables are both things:

public class Thing

{

}

public class Animal:Thing

{

public int Weight;

}

public class Vegetable:Thing

{

public int Length;

}

And we execute the Go() method below:

public void Go()

{

Thing x = new Animal();

Switcharoo(ref x);

Console.WriteLine(

"x is Animal    :   "

+ (x is Animal).ToString());

Console.WriteLine(

"x is Vegetable :   "

+ (x is Vegetable).ToString());

}

public void Switcharoo(ref Thing pValue)

{

pValue = new Vegetable();

}

Our variable x is turned into a Vegetable.

x is Animal    :   False
x is Vegetable :   True

Let‘s take a look at what‘s happening:

    1. Starting with the Go() method call, the x pointer goes on the stack
    2. The Animal goes on the hea
    3. Starting with the call to Switcharoo() method, the pValue goes on the stack and points to x
    4. The Vegetable goes on the heapthe heap
    5. The value of x is changed through pValue to the address of the Vegetable
    6. If we don‘t pass the Thing by ref, we‘ll keep the Animal and get the opposite results from our code.

      If the above code doesn‘t make sense, check out my article on types of Reference variables to get a better understanding of how variables work with reference types.

      In Conclusion.

      We‘ve looked at how parameter passing is handled in memory and now know what to look out for. In the next part of this series, we‘ll take a look at what happens to reference variables that live in the stack and how to overcome some of the issues we‘ll have when copying objects.

时间: 2024-10-16 15:15:00

堆和栈 原文的相关文章

变量存储区:堆和栈

最近在看PHP源码解析,涉及到堆栈存储区的知识,而我对于这个却不太清楚,因此,看了一下相关资料,总结一下. 栈 栈,存储函数中的局部变量(临时变量),存储函数地址,栈是后进先出的结构,由CPU管理和优化. 使用栈存储变量的优势在于:你不用再管理内存了,不必手动分配内存或释放它,此外,由于CPU相关的优化,读取写入的效率也很高. 关于栈需要注意的一点是:存储在栈上的变量的大小是有限制的,而堆却不是. 堆 堆是计算机内存的一块区域,不会自动为你管理内存,也不是由CPU严格管理的.它是一个更自由的内存

堆VS栈

c#堆VS栈(Part One) 前言 本文主要是讲解C#语言在内存中堆.栈的使用情况,使读者能更好的理解值类型.引用类型以及线程栈.托管堆. 首先感谢原文作者:Matthew Cochran 为我们带来了一篇非常好的文章,并配以大量图示,帮助我们更好的理解堆栈之间的调用,本文是在作者原文的基础上进行内容上的精简以及加入我个人在这方面的理解和注释. 最后要感谢博客园的田志良,当我搜索堆栈内部使用时,搜索到了作者的文章,吸取了大量有用的知识,而且翻译的也非常好.唯一美中不足的可能是仅仅翻译了Mat

(转)内存堆和栈的区别

原文: http://student.csdn.net/link.php?url=http://www.top-e.org%2Fjiaoshi%2Fhtml%2F427.html 在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认识,因为我曾经就是这么想的和汇编语言中的堆栈一词混为一谈.我身边的一些编程的朋友以及在网上看帖遇到的朋友中有好多也说不清

JVM基础概念总结:数据类型、堆与栈、基本类型与引用类型

Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置. 基本类型包括:byte, short, int, long, char, float, double, Boolean, returnAddress 引用类型包括:类类型,接口类型和数组. 堆与栈 堆和栈是程序运行的关键,很有必要把他们的关系说清楚. 栈是运行时的

【转载】堆和栈的内存分配

原文:堆和栈的内存分配 在这个练习中,你会在难度上做一个大的跳跃,并且创建出用于管理数据库的完整的小型系统.这个数据库并不实用也存储不了太多东西,然而它展示了大多数到目前为止你学到的东西.它也以更加正规的方法介绍了内存分配,以及带领你熟悉文件处理.我们实用了一些文件IO函数,但是我并不想过多解释它们,你可以先试着自己理解. 像通常一样,输入下面整个程序,并且使之正常工作,之后我们会进行讨论: #include <stdio.h> #include <assert.h> #inclu

什么是堆和栈,它们在哪儿?

问题描述 编程语言书籍中经常解释值类型被创建在栈上,引用类型被创建在堆上,但是并没有本质上解释这堆和栈是什么.我仅有高级语言编程经验,没有看过对此更清晰的解释.我的意思是我理解什么是栈,但是它们到底是什么,在哪儿呢(站在实际的计算机物理内存的角度上看)? 在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗? 它们的作用范围是什么? 它们的大小由什么决定? 哪个更快? 答案一 栈是为执行线程留出的内存空间.当函数被调用的时候,栈顶为局部变量和一些 bookkeeping 数据预留

内存堆和栈的区别

原文: http://student.csdn.net/link.php?url=http://www.top-e.org%2Fjiaoshi%2Fhtml%2F427.html 在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认识,因为我曾经就是这么想的和汇编语言中 的堆栈一词混为一谈.我身边的一些编程的朋友以及在网上看帖遇到的朋友中有好多也说不

C语言中堆和栈的区别

原文:http://blog.csdn.net/tigerjibo/article/details/7423728 一.前言: C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件由栈,堆,数据段(由三部分部分组成:只读数据段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成,如下图所示: 1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值.其操作方式类似于数据结构中的栈. 2.堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄

iOS开发中的内存分配(堆和栈)

进程的内存分区 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的. 进程内存区域.png 代码区:代码段是用来存放可执行文件的操作指令(存放函数的二进制代码),也就是说是它是可执行程序在内存种的镜像.代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作--它是不可写的. 全局(静态)