如果有不理解的地方,可以先看之前的两篇基础
C# 变量入门趣谈
线程堆栈(Thread Stack)和托管堆(Managed Heap)
C#方法传参原理(内存地址原理剖析)
需要先说明一下,此文所说的对象赋值和上文说的方法传参是一致的,方法传参其实就是把一个对象赋值给方法中的参数罢了。
值类型和引用类型
值类型和引用类型有什么根本的区别?
值类型以结构类型为例
在程序还没有执行到声明变量的时候,已经在内存的线程栈里,给所有的局部变量分配了内存空间地址,因为我的程序是x84,32位的,每个int是4个字节,所以每个栈空间的局部变量的内存地址,先压栈p1为0x00cfec1c,p1.x偏移了4个字节为0x00cfec20(对象p1和字段p1.x共占8个字节).而压栈p2的时候,p2的地址为p1的地址-8个,因为p2.x也要占4个字节,往后偏移4个字节.(如何理解压栈:你可以把它想象成一个箱子 数据就像你手中的包子 箱子本来是空的 你放个包子进去 包子就在箱子最底下了;再放包子进去 新放进去的包子就在第一个包子的上面点了 这个放包子的过程就是压栈)
关于偏移量?
对象的地址和成员的地址是有关系的,因此我们在写代码的时候能对象直接点出成员。在x86并且成员都占4个字节的时候,公式是成员n(第n个成员)的堆控件地址是对象地址+(n-1)*4。因为计算机的序号都是从0开始的。
值类型赋值,是复制赋值,赋值的仅仅为栈内存里存的数据。所以赋值后,就是两套地址不同的数据罢了,没有关联。
引用类型以类为例
指令&p可以查看栈地址,*p可以查看堆地址(指针地址)
在程序运行到断点的时候,已为所有的局部变量,在线程栈中都分配好地址。在new p1时才会给p1和p1.x分配了堆空间地址,并存放类型默认值,而p2得等到运行的时候才会分配堆堆(调试的时候会发现)。
这里要着重说一下,类是引用类型。类对象p2的堆地址存放在栈中如上图。当把p1赋值给p2的时候,因为栈内存空间中存放的数据p1对象堆里的地址,所以他就直接拿到p1的地址,于是乎p1和p2的栈内存数据同时指向同一块堆地址,此时,不管怎么改p1或者p2的字段值。他们永远是一个东西。