二进制的形式在内存中绘制一个对象实例

一、引用类型实例的内存布局

从内存布局的角度来看,一个引用类型的实例由如下图所示的三部分组成:ObjHeader + TypeHandle + Fields。前置的ObjHeader用来缓存哈希值和同步状态,TypeHandle部分存储类型对应方法表(Method Table)的地址,方法表可以视为针对类型的描述。也正是这部分内容的存在,运行时可以确定任何一个实例的真实类型,所以我们才说引用类型的实例是自描述(Self Describing)的。Fields用于存储实例每个字段的内容。

image

对于32位(x86)的机器来说,ObjHeader 和 TypeHandle的长度都是4字节。如果是64位(x64)的机器,用于存储方法表地址的TypeHandle 需要8个字节来存储,但是ObjHeader 依然是4个直接。考虑到内存对齐,需要前置4个字节的Padding。对于一个不为null的应用类型变量来说,它存储的是实例的内存地址。但是这个地址并不是实例所在内存的“首地址(ObjHeader)”,而是TypeHandle部分的地址。

二、以二进制的形式创建对象

既然我们已经知道了引用类型实例的内存布局,也知道了引用指向的确切的地址,我们不仅可以采用纯“二进制”的方式在内存“绘制”一个指定引用类型的实例,还可以修改某个变量的“值”指向它。具体的实现体现在如下所示的Create方法中,该方法根据指定的属性值创建一个Foobar对象。除了用来提供两个属性值的foo、bar参数之外,它还通过输出参数bytes返回整个实例的字节序列。

var foobar = Create(1, 2, out var bytes);
Debug.Assert(foobar.Foo == 1);
Debug.Assert(foobar.Bar == 2);

static unsafe Foobar Create(int foo, int bar, out byte[] bytes)
{
    Foobar foobar = null!;
    bytes = new byte[24];
    BinaryPrimitives.WriteInt64LittleEndian(bytes.AsSpan(8), typeof(Foobar).TypeHandle.Value.ToInt64());
    BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(16), foo);
    BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(20), bar);
    Unsafe.Write(Unsafe.AsPointer(ref foobar), new IntPtr(Unsafe.AsPointer(ref bytes[8])));
    return foobar;
}

public class Foobar
{
    public int Foo { get; set; }
    public int Bar { get; set; }
}

根据上述针对内存布局的介绍,我们知道任何一个Foobar实例在x64机器中都映射位一段连续的24字节内存,所以Create方法创建了一个长度位24的字节数组。我们保持ObjHeader为空,所以我们从第8(zero based)个字节开始写入Foobar类型对应TypeHandle的值(8字节),然后将指定的数据成员的值(int类型占据4个字节)填充到最后8个字节(由于两个字段的类型均为int,所以不需要添加额外的“留白”来确保内存对齐)。自此我们将“凭空”在内存中“绘制”了一个Foobar对象。由于x86机器采用“小端字节序”,所以二进制的写入最终是通过调用BinaryPrimitives的WriteInt32/64LittleEndian方法来完成的。

接下来我们定义一个Foobar类型的变量,并让它指向这个绘制的Foobar对象。我们在上面说过,它指向的不是实例内存的首字节,而是TypleHandle部分。对于我们的例子来说,它指向的就是我们创建的字节数组的第8(zero based)的元素。针对变量内容(目标对象的地址)的改写是通过调用Unsafe的静态方法Write实现的。我们的演示程序调用了Create创建了一个Foo和Bar属性分别为1和2的Foobar对象,并得到它真正映射在内存中的字节序列。

三、字节数组与实例状态的同一性

对于我们定义的Create方法来说,由于通过输出参数返回的字节数字就是返回的Foobar对象在内存中的映射,所以Foobar的状态(Foo和Bar属性)发生改变后,字节数组的内容也会发生改变。这一点可以通过如下的程序来验证。

var foobar = Create(1, 1, out var bytes);
Console.WriteLine(BitConverter.ToString(bytes));

foobar.Foo = 255;
foobar.Bar = 255;
Console.WriteLine(BitConverter.ToString(bytes));

输出结果

00-00-00-00-00-00-00-00-D8-11-30-17-F9-7F-00-00-
01-00-00-00-01-00-00-00

00-00-00-00-00-00-00-00-D8-11-30-17-F9-7F-00-00-
FF-00-00-00-FF-00-00-00

既然返回的字节数据和Foobar对象具有同一性,我们自然也可以按照如下的方式通过修改字节数组的内容来到达改变实例状态的目的。

var foobar = Create(1, 1, out var bytes);

Debug.Assert(foobar.Foo == 1); Debug.Assert(foobar.Bar == 1); BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(16), 255);BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(20), 255); Debug.Assert(foobar.Foo == 255); Debug.Assert(foobar.Bar == 255);

四、ObjHeader针对哈希被同步状态的缓存

我们可以进一步利用这种方式验证实例的ObjHeader针对哈希值和同步状态的缓存。如下面的代码片段所示,我们调用Create创建了一个Foobar对象并将得到的字节数组打印出来。然后我们调用其GetHashCode方法触发哈希值的计算,并再次打印字节数组。接下来我们创建一个新的Foobar对象,分别对它进行加锁和解锁状态打印字节数组。

var foobar = Create(1, 2, out var bytes);
Console.WriteLine($"{BitConverter.ToString(bytes)}[Original]");
foobar.GetHashCode();
Console.WriteLine($"{BitConverter.ToString(bytes)}[GetHashCode]");

foobar = Create(1, 2, out bytes);
lock (foobar)
{
    Console.WriteLine($"{BitConverter.ToString(bytes)}[Enter lock]");
}
Console.WriteLine($"{BitConverter.ToString(bytes)}[Exit lock]");

从如下所示的输出结果可以看出,在GetHashCode方法调用和被“锁住”之后,承载Foobar对象的ObjHeader字节(4-7字节)都发生了改变,实际上运行时就是利用它来存储计算出的哈希值和同步状态。

00-00-00-00-00-00-00-00-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Original]
00-00-00-00-
C7-D5-9F-0D
-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[GetHashCode]
00-00-00-00-
01-00-00-00
-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Enter lock]
00-00-00-00-00-00-00-00-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Exit lock]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/155693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023.11.17 -hivesql调优,数据压缩,数据存储

目录 1.hive命令和参数配置 2.hive数据压缩 3.hive数据存储 0.原文件大小 18.1MB 1.textfile行存储格式, 压缩后size:18MB 2.行存储格式:squencefile ,压缩后大小8.89MB​ 3. 列存储格式 orc - ZILIB ,压缩后大小2.78MB 4.列存储格式 orc-snappy ,压缩后大小3.75MB 5…

设计模式-中介者模式-笔记

Medicator中介者模式 动机(Motivation) 在软件构建过程中,经常会出现多个对象相互关联交际的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。 …

简单回顾矩阵的相乘(点乘)230101

[[1 0 1][1 1 0]] [[3 0 0 3][2 2 1 3][1 3 1 1]] [[4. 3. 1. 4.][5. 2. 1. 6.]]乘以 c11 a11*b11 a12*b21 a13*b31 1*3 0*2 1*1 4 c12 a11*b12 a12*b22 a13*b32 1*0 0*2 1*3 3 c13a11*b13 a12*b23a13*b33 c14a11*b14 a12*b24a13*b34 c21a21*b11 a22*b21 a23*b…

【iDRAC】突破错误信息壁垒,利用iDRAC提高效率

序 面对旧服务器上的黄色警示灯,工作人员往往陷入困惑。更糟糕的是,如果该服务器转手多次,缺少root用户密码和IP地址,那么要访问服务器iDRAC就更困难了。但是出现问题的硬件蕴含着重要信息,为了解开这个谜团&#xff…

基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用

基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍IIC总线器件数模芯片PCF8591介绍通过按…

windows11编译ffmpeg

安装msys2,直接https://www.msys2.org/上下载exe安装即可,默认路径; 选择msys2-mingw64启动,将下载源替换为中科大 sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist*pacman -S…

RocketMQ的适用场景有哪些?

程序员的公众号:源1024,获取更多资料,无加密无套路! 最近整理了一波电子书籍资料,包含《Effective Java中文版 第2版》《深入JAVA虚拟机》,《重构改善既有代码设计》,《MySQL高性能-第3版》&…

Leetcode刷题详解——衣橱整理

1. 题目链接:LCR 130. 衣橱整理 2. 题目描述: 家居整理师将待整理衣橱划分为 m x n 的二维矩阵 grid,其中 grid[i][j] 代表一个需要整理的格子。整理师自 grid[0][0] 开始 逐行逐列 地整理每个格子。 整理规则为:在整理过程中&am…

解决:Android TextView 设置斜体后右侧文字被遮挡

一、问题说明 遇到一个比较奇怪的情况&#xff0c;给 TextView 文字设置倾斜后&#xff0c;右侧的文字会被遮挡&#xff0c;感觉这应该是 Android 的一个 bug &#xff01; 上代码&#xff1a; <TextViewandroid:id"id/tv_title"android:layout_width"wra…

【计算机网络笔记】网络地址转换(NAT)

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

GO语言的由来与发展历程

Go语言&#xff0c;也称为Golang&#xff0c;是由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三个大牛于2007年开始设计发明&#xff0c;并于2009年正式对外发布的开源编程语言。 三名初始人的目标是设计一种适应网络和多核时代的C语言&#xff0c;Go语言从C继承了…

Rust 语言中的结构体

目录 1、结构体 2、结构体的定义和实例化 2.1 使用字段初始化简写语法 2.2 使用结构体更新语法从其他实例创建实例 2.3 没有命名字段的元组结构体 2.4 没有任何字段的类单元结构体 2.5 结构体示例程序 3、方法 3.1 关联函数 3.2 多个 impl 块 1、结构体 struct&…

2023年09月 Python(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 以下选项中,不是tkinter变量类型的是?( ) A: IntVar() B: StringVar() C: DoubleVar() D: FloatVar() 答案:D tkinter 无 FloatVar()变量类型。 第2题 关于tkinter,以下说…

C++二分算法:使数组严格递增

涉及知识点 动态规划 二分查找 题目 给你两个整数数组 arr1 和 arr2&#xff0c;返回使 arr1 严格递增所需要的最小「操作」数&#xff08;可能为 0&#xff09;。 每一步「操作」中&#xff0c;你可以分别从 arr1 和 arr2 中各选出一个索引&#xff0c;分别为 i 和 j&#…

OceanBase:中国场景推动树立分布式数据库四项新标准

11月16日&#xff0c;在OceanBase2023年度发布会上&#xff0c;OceanBase CEO杨冰介绍&#xff0c;中国数字经济的蓬勃发展催生了对分布式数据库的强大需求&#xff0c;这种需求也牵引了OceanBase坚定投入自主研发&#xff0c;从而推动树立了分布式数据库的四项新标准。 据了解…

【计算机组成原理】定点加法、减法运算

系列文章目录 绘制出纯整数(1字节)和纯小数的数轴 将十进制数20.59375&#xff0c;转换成754标准的32位浮点数的二进制存储格式 用双符号位补码求 x 0.1010011, y -0.1001010, 分别求出 x y, x - y&#xff0c;并判溢出

单例模式(常用)

单例模式&#xff08;单例设计模式) 在有些系统中&#xff0c;为了节省内存资源、保证数据内容的一致性&#xff0c;对某些类要求只能创建一个实例&#xff0c;这就是所谓的单例模式。 单例模式的定义与特点 单例&#xff08;Singleton&#xff09;模式的定义&#xff1a;指…

Maven项目指定main方法配置

例如有个maven工程 打包后 xxx.jar 而这个maven工程里可能有很多main方法,比如测试的main方法 插件指定 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId>&…

KVM网络环境下vlan和trunk的理解

vmware exsi 平台&#xff0c;虚拟交换机管理界面的上行链路是什么意思 VMware ESXi中的虚拟交换机管理界面中的“上行链路”&#xff08;uplinks&#xff09;是指虚拟交换机连接到物理网络的物理网络适配器。在ESXi中&#xff0c;虚拟交换机&#xff08;vSwitch&#xff09;用…

【人工智能】本地运行开源项目MMSegmentation引发的问题

文章目录 ❌AssertionError: Torch not compiled with CUDA enabled问题描述问题分析解决方案总结参考文献 ❌AssertionError: Torch not compiled with CUDA enabled 问题描述 python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-5…