C#中堆和栈的区别分析(有待更新总结2)

2023-10-07 17:38

本文主要是介绍C#中堆和栈的区别分析(有待更新总结2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C#中堆和栈的区别分析(有待更新总结2) 收藏
线程堆栈:简称栈 Stack
托管堆: 简称堆 Heap

使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切。如果我们写出如下两段代码:
代码段1:

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

代码段2:

public class MyInt
           {        
             public int MyValue;
           }

          public MyInt AddFive(int pValue)
           {
                 MyInt result = new MyInt();
                 result.MyValue = pValue + 5;
                return result;
           }

问题1:你知道代码段1在执行的时候,pValue和result在内存中是如何存放,生命周期又如何?代码段2呢?
要想释疑以上问题,我们就应该对.Net下的栈(Stack)和托管堆(Heap)(简称堆)有个清楚认识,本立而道生。如果你想提高程序性能,理解栈和堆,必须的!
本文就从栈和堆,类型变量展开,对我们写的程序进行庖丁解牛。
C#程序在CLR上运行的时候,内存从逻辑上划分两大块:栈,堆。这俩基本元素组成我们C#程序的运行环境。

一,栈 vs 堆:区别?

        栈通常保存着我们代码执行的步骤,如在代码段1中 AddFive()方法,int pValue变量,int result变量等等。而堆上存放的则多是对象,数据等。(译者注:忽略编译器优化)我们可以把栈想象成一个接着一个叠放在一起的盒子。当我们使用的时候,每次从最顶部取走一个盒子。栈也是如此,当一个方法(或类型)被调用完成的时候,就从栈顶取走(called a Frame,译注:调用帧),接着下一个。堆则不然,像是一个仓库,储存着我们使用的各种对象等信息,跟栈不同的是他们被调用完毕不会立即被清理掉。

如图1,栈与堆示意图

 

(图1)

栈内存无需我们管理,也不受GC管理。当栈顶元素使用完毕,立马释放。而堆则需要GC(Garbage collection:垃圾收集器)清理。


二,什么元素被分配到栈?什么被分配到堆?

当我们程序执行的时候,在栈和堆中分配有四种主要的类型:值类型,引用类型,指针,指令。

值类型:
在C#中,继承自System.ValueType的类型被称为值类型,主要有以下几种(CLR2.0中支持类型有增加):
    * bool
    * byte
    * char
    * decimal
    * double
    * enum
    * float
    * int
    * long
    * sbyte
    * short
    * struct
    * uint
    * ulong
    * ushort

引用类型:
以下是引用类型,继承自System.Object:
    * class
    * interface
    * delegate
    * object
    * string

指针:
在内存区中,指向一个类型的引用,通常被称为“指针”,它是受CLR( Common Language Runtime:公共语言运行时)管理,我们不能显示使用。需要注意的是,一个类型的引用即指针跟引用类型是两个完全不同的概念。指针在内存中占一块内存区,它本身只代表一个内存地址(或者null),它所指向的另一块内存区才是我们真正的数据或者类型。如图2:


(图2)

指令:
后文对指令再做介绍。

三,如何分配?
我们先看一下两个观点:
观点1,引用类型总是被分配在堆上。(正确?)
观点2,值类型和指针总是分配在被定义的地方,他们不一定被分配到栈上。(这个理解起来有点难度,需要慢慢来)

上文提及的栈(Stack),在程序运行的时候,每个线程(Thread)都会维护一个自己的专属线程堆栈。
当一个方法被调用的时候,主线程开始在所属程序集的元数据中,查找被调用方法,然后通过JIT即时编译并把结果(一般是本地CPU指令)放在栈顶。CPU通过总线从栈顶取指令,驱动程序以执行下去。

下面我们以实例来详谈。

还是我们开篇所列的代码段1:

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

      

当AddFive方法开始执行的时候,方法参数(parameters)则在栈上分配。如图3:

(图3)

注意:方法并不在栈中存活,图示仅供参考。
接着,指令指向AddFive方法内部,如果该方法是第一次执行,首先要进行JIT即时编译。如图4:


(图4)

当方法内部开始执行的时候,变量result被分配在栈上,如图5:


(图5)

方法执行完毕,而且方法返回后,如图6所示:

(图6)

在方法执行完毕返回后,栈上的区域被清理。如图7:

(图7)

以上看出,一个值类型变量,一般会分配在栈上。那观点2中所述又做何理解?“值类型和指针总是分配在被定义的地方,他们不一定被分配到栈上”。
原因就是如果一个值类型被声明在一个方法体外并且在一个引用类型中,那它就会在堆上进行分配。
还是代码段2:

public class MyInt
           {        
             public int MyValue;
           }

          public MyInt AddFive(int pValue)
           {
                 MyInt result = new MyInt();
                 result.MyValue = pValue + 5;
                return result;
           }

当线程开始执行AddFive方法的时候,参数被分配到栈上,如图8所示:

(图8)
由于MyInt是一个引用类型,所以它被分配到堆上,并且在栈中生成一个指针(result),如图9:

(图9)
AddFive方法执行完毕时的情况如图10:

(图10)

栈上内存被清理,堆中依然存在,如图11:

 
(图11)

当程序需要更多的堆空间时,GC需要进行垃圾清理工作,暂停所有线程,找出所有不可达到对象,即无被引用的对象,进行清理。并通知栈中的指针重新指向地址排序后的对象。现在我们应该知道,了解栈和堆,对我们开发出高性能程序的重要性。当我们使用引用类型的时候,一般是对指针进行的操作而非引用类型对象本身。但是值类型则操作其本身。
接下来,我们用例子说明这一点。

例1:

public int ReturnValue()
           {
                int x = new int();
                 x = 3;
                int y = new int();
                 y = x;     
                 y = 4;        
                return x;
           }

执行结果为3,稍作修改:

例2:

public class MyInt
           {
                public int MyValue;
           }

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


执行结果为4。

我们来分析下原因,其实例1的跟以下代码所起效用一样:

public int ReturnValue()
           {
                int x = 3;
                int y = x;   
                 y = 4;
                return x;
           }

        
如图12所示,在栈上x和y分别占用一块内存区,互不干扰。

(图12)

而例2,与以下代码所起效用一样:

   public int ReturnValue2()
           {
                 MyInt x;
                 x.MyValue = 3;
                 MyInt y;
                 y = x;               
                 y.MyValue = 4;
                return x.MyValue;
           }
如图13所示,

 
(图13)
栈上的指针x和y指向堆上同一个区域,修改其一必会改变堆上的数据。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Zevin/archive/2010/07/13/5731965.aspx

这篇关于C#中堆和栈的区别分析(有待更新总结2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/159347

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/