人们在理解值类型和引用类型之间的差异时因为 "值类型在栈上分配, 引用类型在堆上分配" 这句话造成了很多混乱. 这完全是不对的, 本文试图澄清这个问题.
变量中有什么?
理解. NET 中内存工作方式的关键是理解变量是什么, 以及它的值是什么. 在最基本的层面上, 变量是变量名和内存之间的关联. 变量的值是与之关联的内存中的内容. 该值占用内存空间的大小和值的解释取决于变量的类型 - 这正是值类型和引用类型之间的差异所在.
引用类型变量的值始终是引用或 null. 如果是引用, 则它必须是与其变量类型兼容的对象的引用. 例如, 以 Stream s 声明的变量 s 的值是 null 或 Stream 类型 (或其兼容类型) 实例的引用. 引用类型变量所占内存空间的大小是引用的大小, 引用的大小在 32 位模式下固定为 4 个字节, 在 64 位模式下固定为 8 个字节.
值类型变量的值始终是其对象本身的值. 例如, 对于给定的结构:
- struct PairOfInts
- {
- public int a;
- public int b;
- }
以 PairOfInts pair 声明的变量 pair 的值是整数对本身, 而不是对一对整数的引用. 其所占内存空间则是两个整数的大小, 即 8 个字节. 请注意, 值类型变量永远不能赋值为 null - 因为这没有任何意义, 值类型变量不是一个引用.
那么东西存放在哪里?
变量的分配位置取决于声明它的上下文:
局部变量在栈上分配. 这包括引用类型变量 - 变量本身位于栈上, 其引用的值分配在堆上. 方法参数也计为局部变量, 但如果使用 ref,out,in 修饰符修饰它们, 则它们不再是原始类型, 而是转换为托管指针类型(Type &), 此时传递的是原变量的指针, 不再是变量本身.
引用类型的对象始终在堆上分配.
值类型的对象始终内联分配. 即在方法中声明的值类型变量在栈上分配, 而作为类的实例字段的值类型变量将在堆上分配.
静态变量在堆上分配, 包括引用类型和值类型中声明的静态变量. 无论创建多少个实例, 静态变量都共享一个内存空间.
上述规则有几个例外: 在使用匿名方法时的外部变量和迭代器中的局部变量会由编译器优化为其它类型的实例字段, 这些变量会转移到堆中分配.
举个例子
上述文字描述可能听起来有点复杂, 但一个完整的例子可以让事情更清楚一些:
- using System;
- struct PairOfInts
- {
- static int counter = 0;
- public int a;
- public int b;
- internal PairOfInts(int x, int y)
- {
- A = x;
- B = y;
- counter++;
- }
- }
- class Test
- {
- PairOfInts pair;
- string name;
- Test(PairOfInts p, string s, int x)
- {
- pair = p;
- name = s;
- pair.a + = x;
- }
- static void Main()
- {
- PairOfInts z = new PairOfInts(1, 2);
- Test t1 = new Test(z, "first", 1);
- Test t2 = new Test(z, "second", 2);
- Test t3 = null;
- Test t4 = t1;
- //XXX
- }
- }
让我们看一下标记 "XXX" 位置时内存中的内容.
在栈上分配一个 PairOfInts 类型的对象, 对应变量 z.
在堆上分配一个 Test 类型的对象, 在栈上分配一个引用指向该对象, 对应变量 t1. 以 32 位模式举例, 该对象在堆中占用 20 个字节: 8 个字节的头信息(所有堆对象都有),8 个字节用于存储 PairOfInts 实例, 4 个字节用于存储字符串引用.
在堆上分配一个 Test 类型的对象, 在栈上分配一个引用指向该对象, 对应变量 t2. 该对象与上面的对象非常相似.
在栈上分配一个引用, 对应变量 t3. 这个引用是 null - 它没有引用任何对象.
在栈上分配一个引用, 对应变量 t4, 并赋值 t1 引用的对象, 此时 t1 和 t4 引用堆内存中的同一个对象.
最后, 在堆内存中有一个静态变量 PairOfInts.counter.
如果您觉得阅读本文对您有帮助, 请点一下 "推荐" 按钮, 您的认可是我写作的最大动力!
作者: Minotauros
来源: https://www.cnblogs.com/minotauros/p/11254159.html