• c#对象的内存结构(学习笔记)


    • C#里用指针

    指针其实就是地址

    定义一个 int*类型指针变量pointNum。

    c#是安全性语言,不允许有指针,所以只能将指针放置在不安全的上下文中

    解决办法:右击项目,选择“属性”,在属性对话框里,选择“生成”选项卡。将允许不安全代码单选框勾选上

    使用地址的代码

     class Program
        {
            static void Main(string[] args)
            {
                int num = 10;
                unsafe { 
                int  * pointNum;//声明一个用来存储int类型数据的地址变量
                pointNum = #//用&取址地址运算符得到num的变量的地址
                    //可以使用pointNum表示num的地址进行访问
                    //所以 *运算符号表示取值运算符,*(地址数)就是地址数表示的位置的数据 (该说法是类比,不严格)
                   //  *pointNum=110;
                }
            }
        }
    • 构造方法的执行过程(对象的创建过程)

    1.给对象分配内存时,将字段分配在堆空间里,方法(属性,索引,事件)都存在线性栈中,每个对象在堆空间内存里首尾各4个字节(基于32进制的),分别是描述对象类型的内存(类似元数据,但是不是元数据),和同步索引块。它们用来

    2.计算机扫描内存时是有一个对齐的概念,32位的计算机扫描单位为4个字节,64位的计算机扫描的单位是4个字节。所以计算机在分配内存时,遵守内存对齐的规范。便于计算机查询地址。比如 字段有char类型 long类型 int类型,先给long类型的字段分配内存,然后再是int类型字段的内存,最后才是char类型

    对象的内存大小满足公式(此为蒋坤老师经过大量数据的统计而得到的公式)

    所有字段所占内存的总和+首尾各4个字节+常数=最小的4的倍数

    其中,常数只是为了满足最小的4的倍数,而分配的。

    class MyClass1
        {
             public int num = 3;
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                MyClass1 c1 = new MyClass1();
                MyClass1 c2 = new MyClass1();
                MyClass1 c3= new MyClass1();
                MyClass1 c4 = new MyClass1();
            }
        }

     

    通过调试,我们发现每个对象的内存地址相差12个字节,我们可以看是否匹配内存公式:

    num字段占4个字节+首尾各各四个字节(8个字节)+常数=0

    所以常数应该为0个字节,这里不需要常数。

    所以我们可以根据公式算出c1的num字段的内存地址为:0x01b5128

    通过查看内存地址 我们验证我们的公式是否正确(需要注意的是,我们再次调试的时候,对象的地址已经改变,所以根据新改变的地址,即新的c1的地址+4=c1的num地址)

    调试后发现,我们公式推出的C1的num字段地址是正确的。

    另一中验证方法,取地址运算,即扫描地址区间的个数

     class MyClass1
        {
             public int num = 3;
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                MyClass1 c1 = new MyClass1();
                MyClass1 c2 = new MyClass1();
                MyClass1 c3= new MyClass1();
                MyClass1 c4 = new MyClass1();
    
                unsafe
                {
                    fixed (int* pointNum = &c1.num)
                    {
                        //将这个变量固定起来,以使它不被垃圾回收,这样它的地址就固定下来。
                        Console.WriteLine(*(pointNum + 3));//将c1.num地址移动三个
                        // 中间越过一个c1对象的尾部地址(4个字节)c2的首部地址(4个字节)
                        // 就到了c2.Num的内存区,所以显示结果为3
                    }
                }
                Console.ReadKey();
            }
        }

    对象内存结构图解

    •  对象字段内存分布的排布
    class MyClass12
        {
            public long l = 20;
            public byte b = 0;
            public char c = 'a';
            public int num = 3;
       
        }

    按照我们一般的思维,各字段内存的排布为:

    此图的排布是错误的。我们先按公式计算 首字节4个+long类型字段8个字节+byte类型1个字节+char类型2个字节+int类型4个字节+尾部字节4个=23, 所以需要加一个1个字节常量总共凑足最小4的倍数24。所以该类的每个对象占24个字节

    class Program
        {
            static void Main(string[] args)
            {
                MyClass2 c1 = new MyClass2();
                MyClass2 c2 = new MyClass2();
                MyClass2 c3 = new MyClass2();
                MyClass2 c4 = new MyClass2();
            }
        }

    调试,四个对象的地址为

    通过计算,发现每个对象的地址相差确实为24。

    定义一个指针变量指向c1.Num,然后调试,在即时窗口中逐步将指针所指向的地址+1,知道加到24为止(也就是见c1对象占的所有内存遍历一次),查看地址里存储的数据,最后发现c1对象里面的字段内存分布为

    那么有继承的时候又是怎样的呢?按上述操作便可以总结出规律。哈哈

  • 相关阅读:
    Spring Cloud Feign 在调用接口类上,配置熔断 fallback后,输出异常
    Spring Cloud Feign 输出日志
    RestTemplate OR Spring Cloud Feign 上传文件
    Springboot swagger2 导出api文档
    springboot + swagger2 生成api文档
    网页输出日志文件
    Mybatis Common Mapper文件
    Java 序列化工具类
    rabbitMQ的三种路由模式
    .net mvc中session的锁机制
  • 原文地址:https://www.cnblogs.com/tobecabbage/p/3528064.html
Copyright © 2020-2023  润新知