[译].Net中的内存-什么分配在了哪里
原文链接:
人们在理解值类型和引用类型之间的差异时因为“值类型在栈上分配,引用类型在堆上分配”这句话造成了很多混乱。这完全是不对的,本文试图澄清这个问题。
变量中有什么?
理解.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
出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
下一篇: Kotlin使用静态变量与静态方法详解