zoukankan      html  css  js  c++  java
  • .NET的堆和栈02,值类型和引用类型参数传递以及内存分配

    在" .NET的堆和栈01,基本概念、值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配。我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收。而本篇的重点要放在:值类型和引用类型参数的传递,以及内存分配。

    主要包括:
    ■  传递值类型参数
    ■  传递容易造成"栈溢出"的值类型参数,在值类型参数前加关键字ref
    ■  传递引用类型参数
    ■  传递引用类型参数,在引用类型参数之前加关键字ref

      传递值类型参数

    class Class1
         {
              public void Go()
              {
                  int x = 5;
                  AddFive(x);
     
                  Console.WriteLine(x.ToString());
                  
              }
     
              public int AddFive(int pValue)
              {
                  pValue += 5;
                  return pValue;
              }
         }

    大致过程如下:

    1、值类型变量x被放到"栈"上。
    8

    2、开始执行AddFive()方法,值类型变量pValue被放到"栈"上,并把x的值赋值给pValue,pValue的值变成了5。
    9

    3、继续执行AddFive()方法,pValue的值变成了10。
    10

    4、执行完AddFive()方法,释放pValue的内存,"栈"指针回到x,线程重新回到Go()方法中。
    11

    输出结果:5


    以上,在传递值类型参数x的时候,实际上是把x一个字节一个字节地拷贝给pValue。

      传递容易造成"栈溢出"的值类型参数,在值类型参数前加关键字ref

    public struct MyStruct
               {
                   long a, b, c, d, e, f, g, h, i, j, k, l, m;
               }
    
              public void Go()
              {
                 MyStruct x = new MyStruct();
                 DoSomething(x);
                  
              }
     
     
               public void DoSomething(MyStruct pValue)
               {
                        // DO SOMETHING HERE....
               }

    假设以上的值类型struct足够大,而x和pValue都会被分配到"栈"上,这时可能造成"栈溢出"。

    12

    如何避免呢?
    --解决办法是让DoSomething传递一个ref类型参数。这样写:

    public struct MyStruct
               {
                   long a, b, c, d, e, f, g, h, i, j, k, l, m;
               }
    
              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;
              }

    使用ref后,执行DoSomething(ref x),是把x的地址赋值给了pValue,即pValue和x指向了同一个引用地址。当改变pValue的值,变化也会反映到x中。         

    13

    输出结果:12345

    以上,为了避免"大型"值类型参数传递时造成的"栈溢出",可以在值类型前面加ref关键字,于是,在传递值类型参数x的时候,实际上是把x本身的栈地址拷贝给pValue,x和pValue指向同一个栈地址。

      传递引用类型参数

    传递引用类型参数的道理和在传递的值类型参数前面加ref关键字是一样的。

    public class MyInt
               {
                   public int MyValue;
               }
    
              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;
               }

    输出结果:12345     

    以上大致过程是这样:
    1、在托管堆上创建一个MyInt类型的实例
    2、在栈上创建一个MyInt类型的变量x指向堆上的实例
    3、把托管堆上的公共字段MyValue赋值为2
    4、通过DoSomething(x)方法,把x的引用地址赋值给pValue,即pValue和x指向同一个引用地址
    5、改变pValue的值,也会反映到x上

    以上,在传递引用类型参数x的时候,实际上是把x指向托管堆实例的引用地址拷贝给pValue,x和pValue指向同一个托管堆实例地址。

      传递引用类型参数,在引用类型参数之前加关键字ref

    public class Thing
               {
               }
     
               public class Animal:Thing
               {
                   public int Weight;
               }
     
               public class Vegetable:Thing
               {
                   public int Length;
               }
    
              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 is Animal    :   False
    x is Vegetable :   True

    以上大致过程是这样:
    1、在托管堆上创建Animal对象实例。
    2、在栈上创建类型为Thing的x变量指向Animal实例的引用地址。
    3、通过Switcharoo(ref x)方法把x本身的地址赋值给pValue,至此,pValue和x指向了相同的栈内存地址,任何一方的变化都会反映到另外一方。

    14

    4、在Switcharoo(ref Thing pValue)内部,在托管堆上创建Vegetable对象实例。
    5、pValue指向Vegetable实例,也就相当于x指向Vegetable实例。

    15

    以上,当在引用类型参数之前加上关键字ref,再传递,是把x本身的栈地址拷贝给pValue,x和pValue指向同一个栈地址。

    参考资料:
    C# Heap(ing) Vs Stack(ing) in .NET: Part II
    《你必须知道的.NET(第2版)》,作者王涛。

    ".NET的堆和栈"系列包括:

    .NET的堆和栈01,基本概念、值类型内存分配

    .NET的堆和栈02,值类型和引用类型参数传递以及内存分配

    .NET的堆和栈03,引用类型对象拷贝以及内存分配

    .NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

  • 相关阅读:
    Javascript 与 c# 数据加密互通 DEC
    Auto.js 隐藏日志信息、定时器
    Auto.js 检测开启无障碍
    Auto.js 初学碰到的坑
    SmartAssembly 汉化说明
    espcms 表结构说明
    处理器调度
    进程死锁与避免
    并发程序设计
    Mysql性能调优
  • 原文地址:https://www.cnblogs.com/darrenji/p/3847821.html
Copyright © 2011-2022 走看看