尽管在.NET framework下我们并不需要担心内存管理和垃圾回收(Garbage Collection),但是我们还是应该了解它们,以优化我们的应用程序。同时,还需要具备一些基础的内存管理工作机制的知识,这样能够有助于解释我们日常程序编写中的变量的行为。在本文中我将讲解我们必须要注意的方法传参的行为。

在第一部分里我介绍了栈和堆的基本功能,还介绍到了在程序执行时值类型和引用类型是如何分配的,而且还谈到了指针。

* 参数,大问题

这里有一个代码执行时的详细介绍,我们将深入第一部分出现的方法调用过程...

当我们调用一个方法时,会发生以下的事情:

1.方法执行时,首先在栈上为对象实例中的方法分配空间,然后将方法拷贝到栈上(此时的栈被称为帧),但是该空间中只存放了执行方法的指令,并没有方法内的数据项。
2.方法的调用地址(或者说指针)被放置到栈上,一般来说是一个GOTO指令,使我们能够在方法执行完成之后,知道回到哪个地方继续执行程序。(最好能理解这一点,但并不是必须的,因为这并不会影响我们的编码)
3.方法参数的分配和拷贝是需要空间的,这一点是我们需要进一步注意。
4.控制此时被传递到了帧上,然后线程开始执行我们的代码。因此有另一个方法叫做"调用栈"。

示例代码如下:

此时栈开起来是这样的:

就像第一部分讨论的那样,放在栈上的参数是如何被处理的,需要看看它是值类型还是引用类型。值类型的值将被拷贝到栈上,而引用类型的引用(或者说指针)将被拷贝到栈上。

* 值类型传递

首先,当我们传递一个值类型参数时,栈上被分配好一个新的空间,然后该参数的值被拷贝到此空间中。

来看下面的方法:

方法Go()被放置到栈上,然后执行,整型变量"x"的值"5"被放置到栈顶空间中。

然后AddFive()方法被放置到栈顶上,接着方法的形参值被拷贝到栈顶,且该形参的值就是"x"的拷贝。

当AddFive()方法执行完成之后,线程就通过预先放置的指令返回到Go()方法的地址,然后从栈顶依次将变量pValue和方法AddFive()移除掉:

所以我们的代码输出的值是"5",对吧?这里的关键之处就在于任何传入方法的值类型参数都是复制拷贝的,所以原始变量中的值是被保留下来而没有被改变的。

必须注意的是,如果我们要将一个非常大的值类型数据(如数据量大的struct类型)入栈,它会占用非常大的内存空间,而且会占有过多的处理器周期来进行拷贝复制。栈并没有无穷无尽的空间,它就像在水龙头下盛水的杯子,随时可能溢出。struct是一个能够存放大量数据的值类型成员,我们必须小心地使用。

这里有一个存放大数据类型的struct:

public struct MyStruct
{
    long a, b, c, d, e, f, g, h, i, j, k, l, m;
}

来看看当我们执行了Go()和DoSometing()方法时会发生什么:

public void Go()
{
    MyStruct x = new MyStruct();
    DoSomething(x);
}

public void DoSomething(MyStruct pValue)
{
    // DO SOMETHING HERE....
}

这将会非常的低效。想象我们要是传递2000次MyStruct,你就会明白程序是怎么瘫痪掉的了。

那么我们应该如何解决这个问题?可以通过下列方式来传递原始值的引用:

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....
}

通过这种方式我们能够提高内存中对象分配的效率。

唯一需要注意的是,在我们通过引用传递值类型时我们会修改该值类型的值,也就是说pValue值的改变会引起x值的改变。执行以下代码,我们的结果会变成"123456",这是因为pValue实际指向的内存空间与x变量声明的内存空间是一致的。

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;
}

* 引用类型传递

传递引用类型参数的情况类似于先前例子中通过引用来传递值类型的情况。

如果我们使用引用类型:

public class MyInt
{
    public int MyValue;
}

然后调用Go()方法,MyInt对象将放置在堆上:
public void Go()
{
    MyInt x = new MyInt();
}

如果我们执行下面的Go()方法:

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;
}

将发生这样的事情...

1.方法Go()入栈
2.Go()方法中的变量x入栈
3.方法DoSomething()入栈
4.参数pValue入栈
5.x的值(MyInt对象的在栈中的指针地址)被拷贝到pValue中

因此,当我们通过MyInt类型的pValue来改变堆中MyInt对象的MyValue成员值后,接着又使用指向该对象的另一个引用x来获取了其MyValue成员值,得到的值就变成了"12345"。

而更有趣的是,当我们通过引用来传递一个引用类型时,会发生什么?

让我们来检验一下。假如我们有一个"Thing"类和两个继承于"Thing"的"Animal"和"Vegetable" 类:

public class Thing
{
}
public class Animal : Thing
{
    public int Weight;
}
public class Vegetable : Thing
{
    public int Length;
}

然后执行下面的Go()方法:


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();
}

变量x被返回为Vegetable类型。
x is Animal    :   False
x is Vegetable :   True

让我们来看看发生了什么:

1.Go()方法入栈
2.x指针入栈
3.Animal对象实例化到堆中
4.Switcharoo()方法入栈
5.pValue入栈且指向x

6.Vegetable对象实例化到堆中
7.x的值通过被指向Vegetable对象地址的pValue值所改变。

如果我们不使用Thing的引用,相反的,我们得到结果变量x将会是Animal类型的。

如果以上代码对你来说没有什么意义,那么请继续看看我的文章中关于引用变量的介绍,这样能够对引用类型的变量是如何工作的会有一个更好的理解。

我们看到了内存是怎样处理参数传递的,在系列的下一部分中,我们将看看栈中的引用变量发生了些什么,然后考虑当我们拷贝对象时是如何来解决某些问题的。

转载于:https://www.cnblogs.com/dancingfeather/archive/2008/06/07/1215556.html

.NET中栈和堆的比较(二)相关推荐

  1. .NET中栈和堆的比较【转自:c#开发园地】

    本文转自:C#开发园地 原文翻译的地址: http://www.cnblogs.com/c2303191/articles/1065675.html 压栈(入栈)=执行方法中的指令 .NET中栈和堆的 ...

  2. java中栈和堆都存哪些东西_java中栈内存与堆内存(JVM内存模型)

    java中栈内存与堆内存(JVM内存模型) Java中堆内存和栈内存详解1 和 Java中堆内存和栈内存详解2 都粗略讲解了栈内存和堆内存的区别,以及代码中哪些变量存储在堆中.哪些存储在栈中.内存中的 ...

  3. linux java 栈_关于Java中栈与堆的思考

    1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. 2. 栈的优势是,存取速度比堆要快,仅次于直接位于C ...

  4. JAVA中栈和堆总结

    堆栈空间分配 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由O ...

  5. Java中栈、堆和常量池

    2019独角兽企业重金招聘Python工程师标准>>> Java内存分配主要包括以下几个区域: 寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.. 栈 存放基 ...

  6. .NET中栈和堆的比较 #1

    原文出处:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.a ...

  7. .NET中栈和堆的比较1

    原文出处: http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory. ...

  8. .net/c#中栈和堆的区别及代码在栈和堆中的执行流程详解之一(转)

    http://www.codingthink.com/c/20121223/201212231458171.html 原文出处: http://www.c-sharpcorner.com/Upload ...

  9. java 栈 堆 区别_java中栈与堆的区别

    1. 栈(stack)与堆(heap)都是Java用来在Ram(random access memory随机存取器)中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. ...

最新文章

  1. 字节跳动秋招超6000人,渣本双非的出路都被谁堵死了?
  2. 接上一篇配置多仓库相关命令
  3. C# GDI+编程(二)
  4. C# 中奇妙的函数 -- 1. ToLookup
  5. pysvn安装及常用方法
  6. discuz!nt论坛搬迁后出错,提示:对象名 'dnt_templates' 无效
  7. 树莓派使用STEP4:安装vim
  8. paurse java_Java学习笔记一
  9. 11个恶意python包被指窃取 Discord 令牌、安装shell
  10. 谷歌再次修复已遭利用的两枚高危0day (CVE-2020-16009/16010)
  11. java 开发小记:如何使用 MyEclipse 开发自己的类库(mylib.jar)以及引用(使用)她...
  12. Android MVP架构手绘图
  13. java实现模拟多道程序设计
  14. POST 请求的四种提交数据方式
  15. 【控制工程】PID控制的原理和特点
  16. 管理学书籍推荐:这10本优秀的管理类书籍最值得一读
  17. ROS kinetic 机器视觉
  18. 【转载】SPSS数据分析中出现的常见问题总结
  19. 15 ArcGIS JS API 4.17更改测量控件黄白相间的默认样式
  20. [Android]Couldn't load testcpp: findLibrary ret...

热门文章

  1. 什么材质耐酸碱_粘玻璃用什么胶水?选择高透明强力胶水不后悔!
  2. cadence软件_IC苦逼搬运工入职之——Cadence基本操作(1)
  3. autowired 静态方法使用_关于springboot工具类中@Autowired注入bean,用static直接修饰,静态方法使用bean时报空指针异常错误...
  4. jvm类加载机制_JVM 类加载机制
  5. BZOJ-2761-不重复数字
  6. android模拟器 后退键,MainActivity返回键模拟home效果,容易出现的问题
  7. 企业官网示例以及数据库表结构
  8. 网络安全技术文章征稿启事
  9. 函数,名称空间——day11
  10. webpack初学笔记 之 小案例篇demo1