`
灵雨飘零
  • 浏览: 34989 次
  • 性别: Icon_minigender_1
  • 来自: 唐山
文章分类
社区版块
存档分类
最新评论

堆、栈、值类型、引用类型分析总结 Part 2

 
阅读更多

Part 1但中,我们简单介绍了堆栈的功能以及值类型、引用类型在堆栈中的存储位置的问题,也简单介绍了指针是虾米。让我们沿着革命的步伐继续前进!

Parameters, the Big Picture.

我们的代码执行的时候,底层到底有哪些内幕交易在发生呢?当我们调用一个方法时:

  • 栈顶分配控件用来存储执行我们的method所包含的信息,这部分空间叫做栈框(stack frame,详情见地板附录)。这里头有一个指针,指向调用地址。通常这是一个GOTO指令,这样线程执行完毕我们的方法后就知道应该回到哪儿去继续执行下一个栈里头的东东。(其实就是把stack frame删掉)
  • 方法的参数被完全复制。这部分我们会做详细解释。
  • 控制权被交给JIT编译好的方法指令集,开始真正的执行代码。实际上,在调用栈中还有另外一个方法,存在于栈框里。(见附录)

代码又来了:

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

这时我们的栈看起来这酱紫滴:

heapvsstack2-1

就像前面说过的那样,参数如果是值类型,内容会被完整的复制过来。如果是引用类型,被复制的则只是指向堆里头实例的一个指针。

Passing Value Types.

首先,当我们传递一个值类型的时候,栈顶分配相应大小的空间,并且把值完整复制过去。例如:

class Class1
{
     public void Go()
     {
         int x = 5;
         AddFive(x); 

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

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

当方法被执行时,会给x分配空间,并且值5被赋值到分配好的空间里。

heapvsstack2-2

接下来,AddFive被压栈,同时其参数也会压栈(分配空间),然后参数的值会从x,一个字节一个字节的复制过来。

heapvsstack2-3

当AddFive结束执行,线程转回到Go方法中。因为AddFive已经没有利用价值了,因此它的参数pValue也跟着没用了,“删之”。

heapvsstack2-4

所以我们代码的输出应该是5,对不?重点是任何值类型用这种方式传递进方法都是传递的其一个复制本,同时我们希望原先的变量里的值保持不变。

但是要记住的是,如果我们的值类型是个相当大(占用空间)的东东,例如一个很大的struct,那么对其进行复制操作将是非常耗费时间的。同时栈空间并不是无限大的,就像瓶子从水龙头接水,总是会溢出的哦!struct经常可能会变得很大,因此要小心使用。多大才算大?这就是个大大大的struct咯(有那么大嘛……*(*&@#¥%):

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

看看使用这个struct时发生了虾米:

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

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

heapvsstack2-5

这可真是效率低下啊。想像一下,如果有数千个对这个struct的调用,那将会是多恐怖的事情!

那可咋办呢?我的程序就是要用到几千次的嘛?答案就是传递一个对这个值类型的引用而不是值类型本身:

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

这样我们就能避免无效率的分配内存了。

heapvsstack2-6

现在我们要小心的问题变成了,如果使用引用传递参数,那么我们操作的就是原来值类型中的值了。也就是说,如果我们改变pValue的值,那么x也跟着变了。看看以下的代码,我们得到的结果将是12345,因为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;
}

Passing Reference Types.

传递引用类型跟使用引用传递值类型其实基本上差球不多。

如果我们在其中使用值类型:

public class MyInt
{
    public int MyValue;
}

并且调用Go方法,MyInt存在于堆上因为其是引用类型:

public void Go()
{
    MyInt x = new MyInt();
}

heapvsstack2-7

如果我们把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;
}

这时情形如下图:

heapvsstack2-8

  1. 开始调用Go时变量x压栈
  2. 调用DoSomething时参数pValue压栈
  3. x的值(MyInt在栈中的地址)被复制给pValue

这样就能解释为什么我们改变作为引用类型属性的值类型MyValue时,结果得到的是改变以后的12345了。

有意思的是,当我们使用引用传递一个引用类型的时候,会发生什么呢?

试试吧。假设有下列引用类型:

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

看看都怎么回事:

heapvsstack2-9

  1. 开始执行Go方法,这时x指针存在于栈中
  2. Animal存在于堆中
  3. 执行Switcharoo方法,pValue存在于栈中,同时指向x(引用传递)

heapvsstack2-10

  1. Vegetable在堆中分配(new Vegetable)
  2. pValue指向x,因此改变pValue的内容实际上是去改变x指向的内容,因此x收pValue的影响指向了Vegetable

如果我们不通过引用传递,那么我们将得到相反的结果,x仍然是Animal。(为什么?可以自个儿去画个图哈)

分享到:
评论

相关推荐

    Linux内核网络栈源代码情景分析.part5.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包最后一部分。

    Linux内核网络栈源代码情景分析part1

    对linux内核源码进行详细的分析

    arcgis空间分析.part1

    arcgis空间分析.part1

    恶意代码分析实战.part2

    本书一方面从内容上更侧重于恶意代码分析技术与实践方法,而非各类恶意代码的原理技术与检测对抗方法;另一方面更加侧重实用性,能够引导读者们在实际恶意代码样本分析过程中使用书籍中...共两部分,此为part2,共2部分

    Linux内核网络栈源代码情景分析.part2.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第二部分。

    Linux内核情景分析.part2.rar

    Linux内核情景分析.part2.rarLinux内核情景分析.part2.rarLinux内核情景分析.part2.rarLinux内核情景分析.part2.rarLinux内核情景分析.part2.rarLinux内核情景分析.part2.rar

    系统分析师教程2008版part2

    软考系统分析教程,最新的系统分析师教程2008版,张友生著.pdf格式. 由于文件比较大,所以分成了7个小的rar文件. 分别是: 系统分析师教程2008版part1 系统分析师教程2008版part2 系统分析师教程2008版...

    arcgis空间分析.part2

    arcgis空间分析.part2

    Linux内核网络栈源代码情景分析.part4.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第4部分。

    Linux内核网络栈源代码情景分析.part3.rar

    主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 压缩包的第3部分。

    接口协议栈总结

    ### 接口协议栈总结 #### 一、概述 本文档旨在总结接口协议栈的相关知识点,特别是Simple Control Transmission Protocol (SCTP) 和 MTP3 User Adapter (M3UA) 层的相关信息及其在网络通信中的作用。接口协议栈是...

    算法设计技巧与分析.part2

    算法设计技巧与分析.part2 算法设计技巧与分析.part2

    深入浅出Linux+TCP_IP协议栈 共三卷 part2

    深入浅出Linux+TCP_IP协议栈 共三卷 part2

    Windows内核情景分析.part2.rar

    Windows内核情景分析.part2.rar

    Linux内核情景分析.part3.rar

    Linux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核情景分析.part3.rarLinux内核...

    Linux_内核源代码情景分析.part2.rar

    Linux_内核源代码情景分析.part2 Linux_内核源代码情景分析.part2

    MATLAB神经网络30个案例分析MATLAB中文论坛编著北航出版-源程序22.part2.rar

    MATLAB神经网络30个案例分析MATLAB中文论坛编著北航出版-源程序22.part2.rar 【书本】MATLAB神经网络30个案例分析.part4.rar 【书本】MATLAB神经网络30个案例分析.part3.rar ...

    官方OPC UA标准规范Part1-part14全部部分

    2. **Part 2: Services** - 这部分详细阐述了OPC UA提供的服务集,如读取、写入、订阅、浏览等。服务集定义了客户端和服务器之间的消息交换格式,以实现数据和信息的透明传输。 3. **Part 3: Address Space Model**...

    Linux内核源代码情景分析.part2

    Linux内核源代码情景分析.part2 共3个包 ,这是第2个包,把三个包下完再解包,否则不能解; 这三个包包括 Linux内核源代码情景分析.part1 Linux内核源代码情景分析.part2 Linux内核源代码情景分析.part3

Global site tag (gtag.js) - Google Analytics