当前位置: 首页 > news >正文

浅析值类型与引用类型的内存分配

作者:林立

大家都知道要学好 .NET,深入了解值类型和引用类型是必不可少的。在这里我给大家简单分析一下它们内存分配的区别和联系。

在分析之前,我们先行构造出一个最简单的类引用类型:

复制代码
public class MyClass
{
}

局部变量的声明

在我们使用类型时,代码里面必然少不了变量的声明,我们先看一下方法内的局部变量的声明,请看如下代码:

复制代码
private static void Main()
{
int i;
MyClass mc;
i = 5;
mc = new MyClass();
}

当一个局部变量声明之后,就会在栈的内存中分配一块内存给这个变量,至于这块内存多大,里面存放什么东西,就要看这个变量是值类型还是引用类型了。

l 值类型

如果是值类型,为变量分配这块内存的大小就是值类型定义的大小,存放值类型自身的值(内容)。比如,对于上面的整型变量 i,这块内存的大小就是 4个字节(一个 int型定义的大小),如果执行 i = 5;这行代码,则这块内存的内容就是 5(如图 -1)。

对于任何值类型,无论是读取还是写入操作,可以一步到位,因为值类型变量本身所占的内存就存放着值。

  • 引用类型

如果是引用类型,为变量分配的这块内存的大小,就是一个内存指针(实例引用、对象引用)的大小(在 32位系统上为 4字节,在 64位系统上为 8字节)。因为所有引用类型的实例(对象、值)都是创建在堆上的,而这个为变量分配的内存就存放变量对应在堆上的实例(对象、值)的内存首地址(内存指针),也叫实例(对象)的引用。以图形化的方式展现仿佛是变量有一条线指向着它在堆中的实例(有如图 -2),而如果变量的类型还没有被实例化,则为零地址( null、空引用)。

以下为执行 mc = new MyClass ();代码后,内存中的示例:

由图 -2可知,变量 mc中存放的是 MyClass实例(对象)的对象引用,如果需要访问 mc实例,系统需要首先从 mc变量中得到实例的引用(在堆中的地址),然后用这个引用(地址)找到堆中的实例,再进行访问。需要至少 2步操作才可以完成实例访问。

类型赋值

另一个常见的操作就是类型的赋值操作,即变量之间的赋值。由于值类型和引用类型的变量内部存放的内容不同,导致在变量赋值的时候,会有相同的行为而有不同的结果。

  • 值类型

请看如下代码:

复制代码
private void SomeMethod()
{
int i, j;
i = 5;
j = i;
j = 10;
}

相信大家一定都知道最后的结果是 i:5, j:10。不过在 .NET中, int类型也是一个结构,不但可以存放整数值,还有一系列的方法和属性可以使用,而非我们以前学 C语言时的那种单纯 int存放一个整数的概念。所以我们现在看针对 int的代码,其实也是在看针对 struct类型的代码。

对于值类型的赋值语句“ j = i”,请看图 -3:

在执行 j = i;语句时,变量 i中的内容被复制了一份,然后放到了变量 j中,此时变量 i和 j都有一个值为 5,同时也可以看出, i和 j的值现在互不相干,完全独立,所以任意修改其中的某个变量的值,不会影响到另外一个。

  • 引用类型

请看如下代码:

复制代码
private void SomeMethod()
{
MyClass x, y;
x = new MyClass();
y = x;
}

代码中先对 x进行了实例化,然后将 x赋值到 y,这段代码的结果请看图 -4:

当执行 y = x;代码时,变量 x中的内容同样复制了一份,然后放到了变量 y之中,但是因为变量 x中存放是一个类型实例(对象)的引用,因此这次赋值操作等同于把这个引用传递给了变量 y,结果就是 x和 y中的引用指向堆中同一个类型的实例(对象)。

你可以使用 x的引用去修改 MyClass实例(对象),然后用 y的引用得到修改后的 MyClass实例(对象),反之亦可,因为 x和 y引用的是同一个实例(对象)。

复杂类型的内存布局概述

以上内容是以值类型或者引用类型为一个整体叙述值类型和引用类型的变量声明和赋值的情况。下面我们看看值类型和引用类型内部含有其他类型成员变量(一般称为字段)的情况。虽然看起来情况似乎复杂了一点,但是只要我们可以把握住值类型的值存放在值类型变量内部,而引用类型的值在堆中存放,引用类型的变量只存放对它实例(对象)的引用这个原则,就可以很清晰的做出分析。

  • 值类型

且看下面的类型定义代码:

复制代码
public struct MyStruct
{
/* 注意:作为结构,内部字段是不能象下面所写那样,在声明时直接初始化的。
* 但这里为了节省篇幅,从表达语义的角度,直接在声明时初始化了
* 此结构的代码无法通过编译的 */
public int i = 5; //值类型
public System.Exception ex = new Exception(); //引用类型
}

在 MyStruct结构中,有2个字段,一个是值类型的i变量,一个是引用类型的ex变量。这种情况下,内存中应该是一个什么模样呢?

首先,变量 i和ex作为MyStruct的成员,必然存放在MyStruct实例的内部,而变量i作为值类型,其值就存放在自身;ex作为引用类型,变量内只存放实例(对象)的引用,而实例(对象)则在堆上创建,因此就有如图-5所示:

  • 引用类型

且看下面的类型定义代码:

复制代码
public class MyClass
{
MyStruct ms = new MyStruct();          //上面所述的MyStruct结构
System.Random r = new Random();   //引用类型
}

在 MyClass中,有2个字段成员,一个是我们上面的所定义的MyStruct结构值类型ms,另外一个是Random类类型r。

这里我们把情况再变得复杂一些了,因为 MyStruct内部还有值类型和引用类型的字段,这时候内存中是一幅什么景象呢?我们要记住,不管情况多么复杂,把握住值类型和引用类型的特点,慢慢分析,总会得到正确的结果,正如图-6所示:

作为引用类型的实例(对象),无论什么情况,都是在堆中的。而 MyStruct结构作为MyClass的成员,它也在MyClass实例所占的堆内存中,而且因为值类型的值是在自身存放的,所以就是图-6中看到的结果。整个图-6,所有的值类型和引用类型的布局,都完全负责值类型和引用类型的特点,没有例外。

  • 总结

以前在问起值类型和引用类型有什么区别的时候,经常听到同学说“值类型存放在栈上,引用类型存放在堆上”。其实这么说并不严谨,因为当值类型作为引用类型的一个成员的时候,它的值是内嵌在引用类型实例内部在堆上存放的。我认为,正确的说法应该是:值类型变量的值存放在变量内部,而引用类型变量的值存放在堆上,变量本身存放一个指向堆中的值的引用。同时我们也可以看到在 2个变量赋值的时候,值类型和引用类型的差别,值类型将自身的值复制给对方,之后,2方互不相干;引用类型把引用复制给对方,从而双方都指向同一个堆中的实例,其中任何一方对实例做出修改,都会在另一方的操作中得到反映。最后我们通过复杂类型的内部成员的内存布局情况,进一步了解了值类型和引用类型的内存布局情况。

转载于:https://www.cnblogs.com/cuihongyu3503319/archive/2009/02/06/1385236.html

相关文章:

  • 在线抓图WebSnap Beta 1.2 更新
  • 软件开发者面试百问(转)
  • tomcat:Cannot get a connection, pool exhausted
  • 网站架构探索(1)---序言 王泽宾
  • asp.net 给button 控件 换个背景图片
  • VSTDB
  • 数组、字节数组、转换等
  • C#基础概念
  • Rational Rose2003安装方法
  • 服务器安装
  • 验证码代码
  • 非常全面和深入的PHP介绍
  • 网站建设-如何选择
  • 如何在WebService中获取客户端的IP地址
  • 聚集索引和非聚集索引的根本区别(转)----收集下
  • FineReport中如何实现自动滚屏效果
  • golang 发送GET和POST示例
  • java 多线程基础, 我觉得还是有必要看看的
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • leetcode46 Permutation 排列组合
  • miaov-React 最佳入门
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • Python学习之路16-使用API
  • SpriteKit 技巧之添加背景图片
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 构建工具 - 收藏集 - 掘金
  • 关于 Cirru Editor 存储格式
  • 7行Python代码的人脸识别
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 国内开源镜像站点
  • 扩展资源服务器解决oauth2 性能瓶颈
  • ​iOS实时查看App运行日志
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #Linux(帮助手册)
  • #NOIP 2014# day.2 T2 寻找道路
  • #QT(一种朴素的计算器实现方法)
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (07)Hive——窗口函数详解
  • (笔试题)合法字符串
  • (算法)前K大的和
  • (学习日记)2024.01.19
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .Net Core 中间件验签
  • .net core开源商城系统源码,支持可视化布局小程序
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • /boot 内存空间不够
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [100天算法】-每个元音包含偶数次的最长子字符串(day 53)
  • [AHOI2009]中国象棋 DP,递推,组合数
  • [Android View] 可绘制形状 (Shape Xml)
  • [Apio2012]dispatching 左偏树
  • [BUUCTF NewStarCTF 2023 公开赛道] week3 crypto/pwn