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

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

从内存布局的角度来看,一个引用类型的实例由如下图所示的三部分组成: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-0000-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]

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

并发编程之生产者消费者模型

什么是生产者消费者模型 生产者消费者模型是多线程中一个比较典型的模型。 打个比方:你是一个客户,你去超市里买火腿肠。 这段话中的 "你"就是消费者, 那么给超市提供火腿肠的供货商就是生产者。超市呢?超市是不是被…

Scrapy----Scrapy简介

文章目录 概述与应用背景架构和组件功能和特点社区生态概述与应用背景 Scrapy,一个高效、灵活、且强大的Web爬取框架,被广泛应用于数据抓取和网页内容的结构化提取。它是用Python编写的,支持多平台运行,适用于数据挖掘、在线零售信息收集、历史数据存档等多种场景。Scrapy…

【ArcGIS处理】行政区划与流域区划间转化

【ArcGIS处理】行政区划与流域区划间转化 引言数据准备1、行政区划数据2、流域区划数据 ArcGIS详细处理步骤Step1:统计行政区划下子流域面积1、创建批量处理模型2、添加批量裁剪处理3、添加计算面积 Step2:根据子流域面积占比均化得到各行政区固定值 参考…

设计基于STM32F103C8T6微控制器的巡线小车

巡线小车是一种能够在一条预定线追踪路径的小车,广泛应用于工业自动化、物流仓储、智能家居等领域。本设计将使用STM32F103C8T6微控制器来实现一个基础的巡线小车。 硬件组成:1. STM32F103C8T6微控制器开发板:作为巡线小车的核心控制器&…

如何快速搭建Spring Boot接口调试环境并实现公网访问

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

用归并排序算法merge_sort( )求解 逆序对的数量 降低时间复杂度为 nlogn

题目简述 给定一个序列有n个数&#xff0c;求n个数中逆序对的个数&#xff0c;逆序对的定义&#xff1a;i < j && a[i] > a[j]。 输入格式 第一行包含一个整数n。 第二行包含 n 个整数&#xff08;所有整数均在1~1e9范围内&#xff09;&#xff0c;表示整数数…

Maven项目指定main方法配置

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

Python武器库开发-flask篇之模板渲染(二十四)

flask篇之模板渲染(二十四) Flask 中的模板是一种将数据和 HTML 代码组合在一起的方式&#xff0c;使得我们可以生成动态的 HTML 页面。使用模板可以使我们的代码更加简洁、易于维护和复用。在真实的环境中&#xff0c;我们往往接触到的是由 html、CSS和JavaScript所做的网页&…

【教3妹学编程-算法题】购买物品的最大开销

3妹&#xff1a;2哥&#xff0c;听说你今天发工资啦&#xff1f; 请我吃饭怎么样&#xff0c;嘿嘿 2哥 : 切&#xff0c;你上周还发工资了呢&#xff0c;也没见你请我吃饭。 3妹&#xff1a;哎呀&#xff0c; 我的工资都用来双11 shopping了&#xff0c; 双11过后我都吃了1周土…

【转载】快速搭建OpenGL环境!!!Windows10 + Visual Studio 2019 搭建OpenGL环境

目录 具体链接&#xff1a; 【转自】 完成后效果如下&#xff1a; 一开始的爆红&#xff1a; 下载安装后&#xff1a; 运行成功&#xff1a; 具体链接&#xff1a; 萌新向&#xff01;&#xff01;&#xff01;Windows10 Visual Studio 2019 搭建OpenGL环境&#xff08;…

基于springboot实现体育场馆运营平台项目【项目源码】

基于springboot实现体育场馆运营管理系统演示 系统开发平台 在该数码论坛系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能…

2024清理mac苹果电脑内存免费工具CleanMyMac X4.15

当你使用苹果电脑时&#xff0c;内存的优化和清理变得至关重要。随着时间的推移&#xff0c;我们的电脑内存可能会变得拥挤&#xff0c;导致性能下降。清理内存可以提高电脑的速度和反应能力&#xff0c;并确保它始终在良好状态下运行。本文将向您介绍怎么清理苹果电脑内存的方…

深眸科技革新升级OCR技术,与AI视觉实现有效融合赋能各行业应用

OCR即光学字符识别&#xff0c;是通过扫描仪或工业相机等电子设备检查打印的字符&#xff0c;并通过检测暗、亮的模式确定其形状&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程。 目前&#xff0c;随着机器视觉和人工智能技术的进一步升级&#xff0c;OCR技术实…

鸿蒙原生应用开发-折叠屏、平板设备服务卡片适配

一、多设备卡片适配原则 为不同尺寸的卡片提供不同的功能 在卡片开发过程中请考虑适配不同尺寸的设备&#xff0c;特别是在折叠屏和平板设备上&#xff0c;设备屏幕尺寸的变化直接影响了卡片内容的展示。请发挥想象力设计具有自适应能力的卡片&#xff0c;避免在卡片内容不做…

代码随想录图论|130. 被围绕的区域 417太平洋大西洋水流问题

130. 被围绕的区域 **题目&#xff1a;**给你一个 m x n 的矩阵 board &#xff0c;由若干字符 ‘X’ 和 ‘O’ &#xff0c;找到所有被 ‘X’ 围绕的区域&#xff0c;并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。 题目链接&#xff1a;130. 被围绕的区域 解题思路&#xff1a…

物联网AI MicroPython学习之语法 I2C总线

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; I2C 介绍 模块功能: I2C Master设备驱动 接口说明 I2C - 构建硬件I2C对象 函数原型&#xff1a;I2C(id, scl, sda, freq)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明idintYI2C外设&#xff…

数据结构(c语言版本) 二叉树的遍历

要求 实现二叉树的创建&#xff0c;并输入二叉树数据 然后先序遍历输出二叉树、中序遍历输出二叉树、后序输出二叉树 例如二叉树为&#xff1a; 该二叉树的先序遍历结果为&#xff1a; A B D C E F 该二叉树的中序遍历结果为&#xff1a; B D A E C F 该二叉树的后序遍历结果…

Mybatis的Mapper接口传递多个参数的时候必须要加@Param注解吗?

答案是&#xff1a;不一定&#xff0c;取决于mybatis的版本、jdk的版本和javac的编译选项。 测试代码 Maven依赖&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>…

linux三次握手、四次挥手

TCP协议是一个安全的、面向连接的、流式传输协议&#xff0c;所谓的面向连接就是三次握手&#xff0c;对于程序猿来说只需要在客户端调用connect()函数&#xff0c;三次握手就自动进行了。先通过下图看一下TCP协议的格式&#xff0c;然后再介绍三次握手的具体流程。 1.tcp协议…

kafka个人笔记

大部分内容源于https://segmentfault.com/a/1190000038173886, 本人手敲一边加强印象方便复习 消息系统的作用 解耦 冗余 扩展性 灵活性&#xff08;峰值处理 可恢复 顺序保证 缓冲 异步 解耦&#xff1a;扩展两边处理过程&#xff0c;只需要让他们遵守约束即可冗余&#xf…