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

使用lldb看看Rust的HashMap

目录

前言

正文

读取桶的状态

获取键值对

键值对的指针地址

此时,读取数据

读取索引4的键值对

多添加几个键值对

 使用i32作为键,&str作为值

使用i32作为键,String作为值


前言

前面使用ldb看了看不同的类型,这篇再使用lldb就来看看HashMap

使用lldb查看Rust不同类型的结构-CSDN博客https://blog.csdn.net/qq_63401240/article/details/147839957?spm=1001.2014.3001.5501

正文

use std::collections::HashMap;fn main() {let mut map = HashMap::new();// 插入键值对map.insert("go", "tt");map.insert("66", "123");map.insert("ok", "no");println!("最终的 HashMap: {:?}", map);
}

这段简单地的代码,笔者在println哪里打个断点,然后用lldb查看

首先

p map

效果和expr 的等效的  

(lldb) p map
(std::collections::hash::map::HashMap<ref$<str$>,ref$<str$>,std::hash::random::RandomState>) map = {base = {hash_builder = (k0 = 4722932796647620636, k1 = 13347288390664849048)table = {table = {bucket_mask = 3ctrl = {pointer = 0x000001bc42010e00}growth_left = 0items = 3}alloc = {}marker = {}}}
}

 这个就看出HashMap在Rust的结构吧,笔者也不知道用什么词表示,就用结构这个词表示。

这里最关键的东西,显然易见是pointer,里面是一个地址。

结合HashMap有关的知识
【数据结构】哈希表-CSDN博客https://blog.csdn.net/jmlangel_/article/details/147423680

 7. HashMap的底层数据结构 - 知乎https://zhuanlan.zhihu.com/p/636054806

关注

bucket_mask = 3
ctrl = {
          pointer = 0x000001bc42010e00
    }
        growth_left = 0
        items = 3

这几个的意思

bucket_mask = 3:哈希表有 3+1 个桶。
ctrl.pointer = 0x000001bc42010e00:控制字节数组的起始地址。
growth_left = 0:哈希表已经满了。
items = 3:哈希表中有 3 个键值对 

这里面只有一个地址,就从这里开始

读取桶的状态

memory read -c 4 0x000001bc42010e00

memory read 是lldb命令,-c 4 表示读取4个字节的内容

为什么是4 ,因为有4个桶。

关于桶的状态如下

状态描述控制字节值备注
占用 (Occupied)桶中存储了有效的键值对< 0x80 (高位为 0)通常为哈希值的高 7 位 (hash >> 57),用于快速比较哈希
空 (Empty)桶未被使用,当前没有键值对0x80 (二进制 10000000)表示桶从未存储数据
已删除 (Tombstone)桶曾存储键值对,但数据已被删除0xff (二进制 11111111)用于标记移除的条目,避免破坏哈希表的探查链 (probe chain)

运行结果如下

(lldb) memory read -c 4 0x000001bc42010e00
0x1bc42010e00: 70 ff 06 7f   

70 是小于80,因此是占有,索引为1 。

ff  表示曾经占有 ,

06 是小于80,因此是占有 ,索引为3。

7f 是小于80,因此是占有,索引为4 。

获取键值对

怎么获取键值对,这确实是一个的问题?

笔者添加一个循环,打上断点

    for (k,v) in &map{println!("{:?},{:?}",k,v);}

看看k,v

此时,地址发生变换,重新执行上面的命令

(lldb) memory read -c 4 0x000002a0336c0e00
0x2a0336c0e00: ff 7a 75 4c

发现了变换,还是三个,但是索引是2,3,4了 

ctrl指针的地址是0x2a0336c0e00,笔者复制出来,没有前面的0,不影响

k的地址是          0x2a0336c0dc0

二者之差,等于0x40,变成十进制就是64

有这样一个公式

桶的起始地址 = 桶数组的起始地址 + (桶索引 × 桶的大小)

现在有两个地址,有索引2,笔者的电脑是64位的

&str有两个部分组成,一个指向数据的地址,占8个字节,还有一个是长度u64,也是8个字节,因此,一个&str,占16个字节

同时考虑到键值对都是&str,因此桶的大小为32字节

实际上可以使用std::mem

use std::mem;
println!("(&str, &str) {}", mem::size_of::<(&str, &str)>());

结果如下

(&str, &str) 32

没问题 

同时考虑到索引为2 ,因此2乘以32等于64,两个地址之差正是64

没问题。

经过多次尝试。可以发现

通过ctrl的pointer的地址,减去 (桶索引 × 桶的大小),就可以算出一个地址。这个地址就是键值对的指针地址

键值对的指针地址

再次运行命令,获取索引

现在ctrl pointer的地址变成了0x00000277412c0e00

(lldb) memory read -c 4 0x00000277412c0e00
0x277412c0e00: 17 ff 5d 04

索引为1,3,4

因此0x00000277412c0e00-32=0x00000277412c0de0

同时考虑到笔者是64位的电脑,读取一个指针需要8个字节

因此

(lldb) memory read -c 8 0x00000277412c0de0
0x277412c0de0: 9c 1b e2 61 f6 7f 00 00

这就是指针的地址

笔者的操作系统储存数据的方式是小端序,因此

指针地址为

0x00007ff661e21b9c

此时,读取数据

(lldb) memory read -c 5 0x00007ff661e21b9c
0x7ff661e21b9c: 36 36 31 32 33                                   66123 

为什么读取5个,这是笔者主动选择的,

看到66123,

结合代码,66是键,123是值,读取到键值对

读取索引4的键值对

0x00000277412c0e00 -4*32=0x00000277412c0d80

指针为

(lldb) memory read -c 8 0x00000277412c0d80
0x277412c0d80: a1 1b e2 61 f6 7f 00 00   

地址为0x00007ff661e21ba1

键值对

(lldb) memory read -c 4 0x00007ff661e21ba1
0x7ff661e21ba1: 6f 6b 6e 6f                                      okno

 没问题

看看是否可以修改

(lldb) memory region 0x00007ff661e21ba1
[0x00007ff661e00000-0x00007ff661e2c000) r--

发现不可以修改。没有写的权限

多添加几个键值对

    map.insert("go", "tt");map.insert("66", "123");map.insert("ok", "no");map.insert("hello1","world1");map.insert("hello2","world2");map.insert("hello3","world3");map.insert("hello4","world4");map.insert("hello5","world5");

p map的结果如下

(lldb) p map
(std::collections::hash::map::HashMap<ref$<str$>,ref$<str$>,std::hash::random::RandomState>) map = {base = {hash_builder = (k0 = 12591853735628107626, k1 = 11397258469864990867)table = {table = {bucket_mask = 15ctrl = {pointer = 0x000002ea70e2c090}growth_left = 6items = 8}alloc = {}marker = {}}}
}

可以发现bucket_mask 变成了15,就有16个桶

因此,第一个内存的读取,桶的状态

(lldb) memory read -c 16 0x000002ea70e2c090
0x2ea70e2c090: 4a ff ff 0a 48 15 ff 60 ff 3c 2a ff ff 4d ff ff

索引分别是1、4、5、6、8、10、11、14

桶的大小依然是32,不妨读取索引8

地址为0x000002ea70e2c090 - 0x100 = 0x000002ea70e2bf90

指针地址为

(lldb) memory read -c 8 0x000002ea70e2bf90
0x2ea70e2bf90: c9 1b 48 e9 f6 7f 00 00 

即 0x00007ff6e9481bc9

(lldb) memory read -c 12 0x00007ff6e9481bc9
0x7ff6e9481bc9: 68 65 6c 6c 6f 34 77 6f 72 6c 64 34              hello4world4

看来是hello4这个键。其他同理

 使用i32作为键,&str作为值

看看桶的大小

println!("(i32,&str) {}", mem::size_of::<(i32, &str)>());

(i32,&str) 24

 i32是4字节,Rust 默认会按 8字节对齐,笔者是64位

因此8+16=24

插入键值对

    map.insert(1, "tt");

就一个

关键输出

 ctrl = {pointer = 0x0000013c22361450}

获取桶的状态

(lldb) memory read -c 4 0x0000013c22361450
0x13c22361450: ff ff ff 39       

索引为4

因此,key地址为

0x0000013c22361450 - 4*24

0x0000013c22361450- 0x60 = 0x000001d9c0da13f0

因为key是一个i32

此时这个地址——0x000001d9c0da13f0,就是key的地址

(lldb) p *(0x000001d9c0da13f0 as *mut u8)
(u8) * = 1

value的指针地址就是0x000001d9c0da13f8,不必细说

笔者重新运行。结果如下

(lldb) memory read -c 4 0x000001a895741880
0x1a895741880: ff ff ff 74   
(lldb)  p *(0x000001a895741820 as *mut i32)
(i32) * = 1
(lldb) memory read -c 8 0x000001a895741828
0x1a895741828: b8 1b 80 a2 f7 7f 00 00 
(lldb) memory read -c 2 0x00007ff7a2801bb8
0x7ff7a2801bb8: 74 74                                            tt

没问题

使用i32作为键,String作为值

首先,看看桶的大小

    let size= mem::size_of::<(i32, String)>();println!("size of (i32, String): {}", size);
size of (i32, String): 32

可以得到是32

 插入数据

    // 插入键值对map.insert(1, "tt".to_string());

先打印看看

    for (key, value) in &map {println!("key: {}, value: {}", key, value);}

结果如下 

key是*mut i32

value 是*mut alloc::string::String

key与value差了0x8

直接开始

(lldb) p map
(std::collections::hash::map::HashMap<i32,alloc::string::String,std::hash::random::RandomState>) map = {base = {hash_builder = (k0 = 14409132009700107725, k1 = 6608230683712741401)table = {table = {bucket_mask = 3ctrl = {pointer = 0x000001f8d3781470}growth_left = 2items = 1}alloc = {}marker = {}}}
}

ctrl的地址0x000001f8d3781470

看看桶的状态

(lldb) memory read -c 4 0x000001f8d37814700x1f8d3781470: 5d ff ff ff   

索引为1

因此 key 的地址0x000001f8d3781470-32=0x000001f8d3781450

(lldb) p *(0x000001f8d3781450 as *mut i32)(i32) * = 1

没问题

key是i32,占8个字节,因此value的地址0x000001f8d3781458

同时考虑到value是一个字符串,在前面输出中是*mut alloc::string::String,

因此,笔者尝试

(lldb) p (0x000001f8d3781458 as *mut alloc::string::String)
(*mut alloc::string::String)  = 0x000001f8d3781458

没想到居然成功了

(lldb) p *(0x000001f8d3781458 as *mut alloc::string::String)
(alloc::string::String) * = {vec = {buf = {inner = {ptr = {pointer = {pointer = 0x000001f8d37861f0}_marker = {}}cap = (__0 = 2)alloc = {}}_marker = {}}len = 2}
}

那后面的事情就不必细说了。都有地址了。

(lldb) memory read -c 2 0x000001f8d37861f0
0x1f8d37861f0: 74 74                                            tt 

没问题

笔者感到很奇怪,为什么&str不行

如果改成&str,输出是这样的

但是

(lldb) p *(0x0000015a3ef96190 as *mut &str)error: could not find type "str"

笔者不能理解。

但是对于i32为键, &str为值,获取到了key的地址

    map.insert(1,"ggggg");

比如

(lldb) p *(0x000001f296071820 as *mut i32)
(i32) * = 1

这个0x000001f296071820 就看key的地址

0x000001f296071820+0x8=0x000001f296071828 

0x000001f296071828 就是value的地址

而value是&str,包括两个部分,一个数据指针,一个是长度

总共是16个字节,因此,如下

(lldb) memory read -c 16 0x000001f296071828
0x1f296071828: b8 1b d2 34 f6 7f 00 00 05 00 00 00 00 00 00 00

前8个字节b8 1b d2 34 f6 7f 00 00 ,数据指针,05 就是长度

数据

(lldb) memory read -c 5 0x00007ff634d21bb8
0x7ff634d21bb8: 67 67 67 67 67                                   ggggg

长度,value的地址+8个字节,即

0x000001f296071828 +0x8=0x000001f296071830

(lldb) memory read -c 1 0x000001f296071830
0x1f296071830: 05                                               
(lldb) p *(0x000001f296071830 as *mut u8)
(u8) * = 5

相关文章:

使用lldb看看Rust的HashMap

目录 前言 正文 读取桶的状态 获取键值对 键值对的指针地址 此时&#xff0c;读取数据 读取索引4的键值对 多添加几个键值对 使用i32作为键&#xff0c;&str作为值 使用i32作为键&#xff0c;String作为值 前言 前面使用ldb看了看不同的类型&#xff0c;这篇再使用…...

Oracle版本、补丁及升级(12)——版本体系

12.1. 版本体系 Oracle作为最流行的一款关系数据库软件产品,其拥有自己一套成熟的版本管理体系。具体版本体系以12c为分界线,前后版本体系分别不同。 ​​​​​​​12.1.1. 12c之前版本 12c之前的Oracle,版本共有5位阿拉伯数字组成,其中的每位数字,都有各自的含义,具…...

2025最新免费视频号下载工具!支持Win/Mac,一键解析原画质+封面

软件介绍 适用于Windows 2025 最新5月蝴蝶视频号下载工具&#xff0c;免费使用&#xff0c;无广告且免费&#xff0c;支持对原视频和封面进行解析下载&#xff0c;亲测可用&#xff0c;现在很多工具都失效了&#xff0c;难得的几款下载视频号工具&#xff0c;大家且用且珍…...

在 Ubuntu 中配置 Samba 实现「特定用户可写,其他用户只读」的共享目录

需求目标 所有认证用户可访问 Samba 共享目录 /path/to/home&#xff1b;**仅特定用户&#xff08;如 developer&#xff09;**拥有写权限&#xff1b;其他用户仅允许读取&#xff1b;禁止匿名访问。 配置步骤 1. 设置文件系统权限 将目录 /home3/guest 的所有权设为 develo…...

Newton GPU 机器人仿真器入门教程(零)— NVIDIA、DeepMind、Disney 联合推出

系列文章目录 目录 系列文章目录 前言 一、快速入门 1.1 实时渲染 1.2 USD 渲染 1.3 示例&#xff1a;创建一个粒子链 二、重要概念 三、API 参考 3.1 求解器 3.1.1 XPBD 求解器 3.1.2 VBD 求解器 3.1.3 MuJoCo 求解器 3.2 关节控制模式 四、Newton 集成 4.1 Is…...

《零基础学机器学习》学习大纲

《零基础学机器学习》学习大纲 《零基础学机器学习》采用对话体的形式&#xff0c;通过人物对话和故事讲解机器学习知识&#xff0c;使内容生动有趣、通俗易懂&#xff0c;降低了学习门槛&#xff0c;豆瓣高分9.1分&#xff0c;作者权威。 接下来的数篇文章&#xff0c;我将用…...

CSS 基础知识分享:从入门到注意事项

什么是CSS&#xff1f; CSS是用于描述HTML或XML文档呈现方式的语言。它控制网页的布局、颜色、字体等视觉表现&#xff0c;让内容与表现分离。 通俗的说&#xff0c;html是骨头&#xff0c;那么css就是他的画皮。 基本语法 CSS规则由两部分组成&#xff1a;选择器和声明块。…...

深入浅出理解JavaScript原型与原型链

先让我们结合生活案例理解原型原型链相关概念,想象一下一个大家庭,有很多成员。 1. 原型 (Prototype) - 家族的共同特征或技能模板 概念对应: 家族中代代相传的共同特征、习惯、或者家族里独有的某个手艺或知识。例子: 假设你们家族的成员普遍都有高个子、善于烹饪一道祖传菜…...

重操旧业,做起了OnlineTool.cc在线工具站

最近闲来无事&#xff0c;做了个在线工具站。 工具不多&#xff0c;起码有&#xff1a;当前IP查询&#xff0c;QRCode二维码生成&#xff0c;图片压缩&#xff0c;JSON格式化&#xff0c;简体繁体转换&#xff0c;等。 使用Astro框架React&#xff0c;Caddy&#xff0c;目前是…...

vue 中的数据代理

在 Vue 中&#xff0c;数据代理&#xff08;Data Proxy&#xff09; 是 Vue 实现 MVVM 模式 的关键技术之一。Vue 使用数据代理让你可以通过 this.message 访问 data.message&#xff0c;而不需要写 this.data.message —— 这大大简化了模板和逻辑代码。 我们来深入理解它的本…...

ubuntu安装Go SDK

# 下载最新版 Go 安装包&#xff08;以 1.21.5 为例&#xff09; wget https://golang.google.cn/dl/go1.21.5.linux-amd64.tar.gz # 解压到系统目录&#xff08;需要 root 权限&#xff09; sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz # 使用 Go 官方安装脚本…...

【C++】学习、项目时Debug总结

这里写目录标题 1. 内存问题1.1. 内存泄漏1.1.1. 内存泄漏案例检查方法1.1.2. 主线程提前退出导致【控】1.1.3. PostThreadMessage失败导致的内存泄漏**【控】**1.1.4. SendMessage 时关闭客户端【控】1.1.5. 线程机制导致【**控】**1.1.6. exit&#xff08;0&#xff09;导致【…...

26考研——中央处理器_指令流水线_指令流水线的基本概念 流水线的基本实现(5)

408答疑 文章目录 六、指令流水线指令流水线的基本概念流水线的基本实现流水线设计的原则流水线的逻辑结构流水线的时空图表示 八、参考资料鲍鱼科技课件26王道考研书 六、指令流水线 前面介绍的指令都是在单周期处理机中采用串行方法执行的&#xff0c;同一时刻 CPU 中只有一…...

Flutter——数据库Drift开发详细教程(三)

目录 参考正文核心API写入&#xff08;更新、插入、删除&#xff09;1.更新和删除2.使用 SQL 表达式更新3.插入件4.更新插入5.返回 参考 https://drift.simonbinder.eu/dart_api/writes/#updating-with-sql-expressions 正文核心API 写入&#xff08;更新、插入、删除&#…...

AI Agent-基础认知与架构解析

定义 AI Agent 可以理解为一种具备感知、决策和行动能力的智能实体&#xff0c;能够在复杂的环境中自主运行&#xff0c;并根据环境变化动态调整自身行为&#xff0c;以实现特定目标。与传统的人工智能程序相比&#xff0c;AI Agent 具有更强的自主性、交互性和适应性。它不仅能…...

privateGPT和RAGflow之间的区别

PrivateGPT和RAGFlow都是基于RAG(检索增强生成)技术的开源项目,但它们在设计目标、技术架构和应用场景上有显著差异。以下是两者的详细对比分析: 1. 核心定位与设计目标 PrivateGPT 隐私优先:专注于完全离线的私有化部署,确保用户数据不离开本地环境,适合对隐私要求极高…...

C语言--字符函数

C语言--字符函数 一、字符函数1.1 iscntrl1.2 isspace1.3 isdigit1.4 isxdigit1.5 islower1.6 isupper1.7 isalpha1.8 isalnum1.9 ispunct1.10 isgraph1.11 isprint 在编程的过程中&#xff0c;我们会经常处理字符&#xff0c;为了方便操作&#xff0c;C语言标准库中提供了一系…...

Android对工程中的String中文字符的整理

​ 本文主要介绍使用python快速整理工程中的中文字符&#xff0c;为app国际化提供便利。 1. 查找Android工程中的所有中文字符串(find_chinese.py) import os import re import argparsedef is_comment_line(line, file_ext):"""判断一行是否是注释:param lin…...

菜鸟之路Day30一一MySQL之DMLDQL

菜鸟之路Day30一一MySQL之DML&DQL 作者&#xff1a;blue 时间&#xff1a;2025.5.8 文章目录 菜鸟之路Day30一一MySQL之DML&DQL一.DML0.概述1.插入语句&#xff08;insert&#xff09;2.更新语句&#xff08;update&#xff09;3.删除语句&#xff08;delete&#xf…...

集团云解决方案:集团企业IT基础架构的降本增效利器

在当今数字化飞速发展的时代&#xff0c;集团企业面临着诸多挑战&#xff0c;尤其是IT基础架构的管理和运营成本居高不下&#xff0c;效率却难以提升。别担心&#xff0c;集团云解决方案的出现为集团企业带来了全新的曙光&#xff0c;真正实现了降本增效&#xff01; 一、集团…...

基 LabVIEW 的多轴电机控制系统

在工业自动化蓬勃发展的当下&#xff0c;多轴伺服电机控制系统的重要性与日俱增&#xff0c;广泛应用于众多领域。下面围绕基于 LabVIEW 开发的多轴伺服电机控制系统展开&#xff0c;详细阐述其应用情况。 一、应用领域与场景 在 3D 打印领域&#xff0c;该系统精确操控打印头…...

SD06_前后端分离项目部署流程(采用Nginx)

本文档详细描述了如何在Ubuntu 20.04服务器上从零开始部署Tlias前后端分离系统。Tlias系统由Spring Boot后端&#xff08;tlias-web-management&#xff09;和Vue前端&#xff08;vue-tlias-management&#xff09;组成。 目录 环境准备安装MySQL数据库部署后端项目部署前端项…...

【kubernetes】通过Sealos 命令行工具一键部署k8s集群

一、前言 1、sealos安装k8s集群官网&#xff1a;K8s > Quick-start > Deploy-kubernetes | Sealos Docs 2、本文安装的k8s版本为v1.28.9 3、以下是一些基本的安装要求&#xff1a; 每个集群节点应该有不同的主机名。主机名不要带下划线。所有节点的时间需要同步。需要…...

《Go小技巧易错点100例》第三十二篇

本期分享&#xff1a; 1.sync.Map的原理和使用方式 2.实现有序的Map sync.Map的原理和使用方式 sync.Map的底层结构是通过读写分离和无锁读设计实现高并发安全&#xff1a; 1&#xff09;双存储结构&#xff1a; 包含原子化的 read&#xff08;只读缓存&#xff0c;无锁快…...

怎么判断是不是公网IP?如何查看自己本地路由器是内网ip还是公网?

在网络世界中&#xff0c;IP 地址如同每台设备的 “门牌号”&#xff0c;起着至关重要的标识作用。而 IP 地址又分为公网 IP 和私网 IP&#xff0c;准确判断一个 IP 属于哪一类&#xff0c;对于网络管理、网络应用开发以及理解网络架构等都有着重要意义。接下来&#xff0c;我们…...

【上位机——MFC】单文档和多文档视图架构

单文档视图架构 特点&#xff1a;只能管理一个文档(只有一个文档类对象) #include <afxwin.h> #include "resource.h"//文档类 class CMyDoc :public CDocument {DECLARE_DYNCREATE(CMyDoc) //支持动态创建机制 }; IMPLEMENT_DYNCREATE(CMyDoc,CDocument) //…...

需求分析阶段测试工程师主要做哪些事情

在软件测试需求分析阶段&#xff0c;主要围绕确定测试范围、明确测试目标、细化测试内容等方面开展工作&#xff0c;为后续测试计划的制定、测试用例的设计以及测试执行提供清晰、准确的依据。以下是该阶段具体要做的事情&#xff1a; 1. 需求收集与整理 收集需求文档&#x…...

Web 实时通信技术:WebSocket 与 Server-Sent Events (SSE) 深入解析

一、WebSocket&#xff1a; &#xff08;一&#xff09;WebSocket 是什么&#xff1f; WebSocket 是一种网络通信协议&#xff0c;它提供了一种在单个 TCP 连接上进行全双工通信的方式。与传统的 HTTP 请求 - 响应模型不同&#xff0c;WebSocket 允许服务器和客户端在连接建立…...

项目模拟实现消息队列第二天

消息应答的模式 1.自动应答: 消费者把这个消息取走了&#xff0c;就算是应答了&#xff08;相当于没有应答) 2.手动应答: basicAck方法属于手动应答(消费者需要主动调用这个api进行应答) 小结 1.需要实现生产者,broker server&#xff0c;消费者这三个部分的 2.针对生产者和消费…...

5.Redission

5.1 前文锁问题 基于 setnx 实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如 HashTable 这样的代码中&#xff0c;他的方法都是使用 sync…...