内存管理-内存分配

一、CLR

CLR即公共语言运行时(Common Language Runtime),是中间语言(IL)的运行时环境,负责将编译后的代码编译成计算机可以识别的机器码,负责资源管理(内存分配和垃圾回收等)。

二、内存分配

我们知道C#对象分为值类型和引用类型两种

值类型:char、int 、float、int、datetime、枚举和结构struct等,值存在于中。

引用类型:类 (Class) 、String、接口、委托和数组等,声明一个引用类型时,先在栈上分配一个空间等待存储堆中的地址,new出这个对象时才分配中的空间,并把堆中的地址赋值给栈。

引用类型分配内存的步骤:

        1、new的时候去对象里面开辟一块内存,分配一个内存地址。

        2、调用构造函数。

        3、把堆地址传递给栈。

需要注意的是,引用类型中的变量(比如int)也是跟随引用类型储存于堆中。

对象初始化顺序

在学习内存分配之前,我们先了解声明一个类时,类中的对象初始化顺序。这对于我们后面理解声明对象时入栈、入堆有帮助。

本类中:

静态变量>静态代码块>成员变量>非静态代码块>构造函数

继承父类:

父类静态变量>父类静态代码块>子类静态变量>子类静态代码块>父类成员变量>父类非静态代码块>父类构造函数>子类成员变量>子类非静态代码块>子类构造函数

1. 栈 先进后出

举个列子来看一下实际内存分配

我们先定义一个 值类型 struct

 public struct ValuePoint
 {
     public int x;
     public ValuePoint(int x)
     {
          this.x = x;
     }
 }

然后在方法里面调用

//先声明变量,没有初始化  但是可以正常赋值  跟类不同
ValuePoint valuePoint;
valuePoint.x = 123;

ValuePoint point = new ValuePoint();
Console.WriteLine(valuePoint.x);

内存分配情况如下

注意:

(1)、值类型分配在栈上面,变量和值都是在栈上面。

(2)、值类型可以先声明变量而不用初始化

2.堆

定义一个引用类型Class

public class ReferencePoint
{
     public int x;
     public ReferencePoint(int x)
     {
           this.x = x;
     }
}

调用此类

 ReferencePoint referencePoint = new ReferencePoint(123);
 Console.WriteLine(referencePoint.x);

 其内存分配如下:

注意:

(1)、引用类型分配在堆上面,变量在栈上面,值在堆上面。

(2)、引用类型分配内存的步骤:

        a、new的时候去对象堆里面开辟一块内存,分配一个内存地址。

        b、调用构造函数。

        c、把堆地址传给栈保存。

3、复杂类型

1.引用类型嵌套值类型

定义一个类,包括一个全局变量和一个局部变量

  public class ReferenceTypeClass
  {
          private int _valueTypeField;
          public ReferenceTypeClass()
          {
              _valueTypeField = 0;
          }
          public void Method()
          {
             int valueTypeLocalVariable = 0;
         }
 }

包括全局变量_valueTypeField和一个值类型的局部变量valueTypeLocalVariabl,那这种情况下内存是如何分配的呢?

值类型不应该是都分配在栈上面吗?为什么一个是分配在堆上面,一个是分配在栈上面呢?

_valueTypeField分配在堆上面比较好理解,因为引用类型是在堆上面分配了一整块内存,引用类型里面的属性也是在堆上面分配内存。

valueTypeLocalVariable分配在栈上面是因为valueTypeLocalVariable是一个全新的局部变量,调用方法的时候,会启用一个线程去调用,线程栈来调用方法,然后把局部变量分配到栈上面。

2.值类型嵌套引用类型

public struct ValueTypeStruct
{
        private object _referenceTypeField;
        public ValueTypeStruct(int x)
        {
            _referenceTypeField = new object();
        }
        public void Method()
        {
            object referenceTypeLocalVariable = new object();
        }
}

从上面的截图中可以看出:值类型里面的引用类型的变量分配在栈上,值分配在堆上。

我们看一个比较特殊的引用类型:String

string student = "大山";

内存分配如下

然后在声明一个变量student2,然后用student给student2赋值:

string student2 = student;

这个时候内存分配情况如下:

student2被student赋值的时候,是在栈上面复制一份student的引用给student2,然后student和student2都是指向堆上面的同一块内存。

然后我们修改一下student2的值

student2 = "App";

输出student和student2的值:

1 Console.WriteLine("student的值是:" + student);
2 Console.WriteLine("student2的值是:"+student2);

从结果中可以看出:student的值保持不变,student2的值变为App,为什么是这样呢?

这是因为string字符串的不可变性造成的。一个string变量一旦声明并初始化以后,其在堆上面分配的值就不会改变了。这时修改student2的值,并不会去修改堆上面分配的值,而是重新在堆上面开辟一块内存来存放student2修改后的值。

修改后的内存分配如下

在看个例子,注意这里比较的实际是内存地址

 string student = "大山";
 string student2 = "App";
 student2 = "大山";
 Console.WriteLine(object.ReferenceEquals(student,student2));

按照上面讲解的,student和student2应该初始化时指向的是不同的内存地址,结果应该是false啊,为什么会是true呢?

这是因为CLR在分配内存的时候,会查找是否有相同的值,如果有相同的值,就重用;如果没有,这时在重新开辟一块内存。

但是其他引用类型和string正好相反。

定义一个类

1 public class Refence
2 {
3      public int Value { get; set; }
4 }

调用这个类

1 Refence r1 = new Refence();
2 r1.Value = 30;
3 Refence r2 = r1;
4 Console.WriteLine($"r2.Value的值:{r2.Value}");
5 r2.Value = 50;
6 Console.WriteLine($"r1.Value的值:{r1.Value}");
7 Console.ReadKey();

从运行结果可以看出,如果是普通的引用类型,如果修改其他一个实例的值,那么另一个实例的值也会改变。正好与string类型相反。

学习好内存分配原则更有利于我们写出高质量的代码,持续学习,持续仅不~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值