作者 | 马超       责编 | 张红月

出品 | CSDN 博客

2020 年转眼间白驹过隙般飞奔而去,在岁末年初的当口,笔者在回顾这一年程序员世界的大事件后,突然发觉如何避免程序员面向监狱编程是个特别值得一谈的话题。

最近我在前辈巨师的带领下,也进入到学习Rust的大军中,与其它语言一样,Rust最初的爬坡难点也在于字符串方面的处理。虽然说Rust与C一样也有指针概念,但是在字符串方面引用了胖指针,关于胖指针的内存布局,被引用最为广泛的一幅说明图如下:

咱们先来说明一下这个胖指针的大致概念,字符串s1有三个元素分别是ptr、len、capacity,其中ptr是指向堆上实际字符串value的指针,len代表字符串的长度,capacity代表字符串的容量。这些值全部都存在栈上,而实际字符串的值则存在堆上。为了让便于说明,我转化了一下上面的图,大家可以看一下这幅图。

对于这幅图的理解真可谓是一波三折,我一开始以为这图画的不对,后来发现应该是对的,最后深入研究还是发现了一个小问题,最终正确的示意图如下:

本文就和大家分享一下具体分析的过程。

胖指针理解错误的起因

我们知道Rust在编译是可以通过-g参数保留符号信息,再通过objdump命令就可以将代码对应的汇编语言导出,具体指令如下:

rustc -g 文件名.rs
objdump -S 文件名

先来看一下代码

fn main() {let mut  s1=String::from("hello");let len = calculate_length(&s1);println!("The Length is {}.",len);}fn calculate_length(s:&String)->usize{s.len()}

将上述代码中字符串值进行微调之后的代码

fn main() {let mut  s1=String::from("hell00");let len = calculate_length(&s1);println!("The Length is {}.",len);}fn calculate_length(s:&String)->usize{s.len()}

在得到相应的汇编代码以后,diff一下结果如下:

2991c2991<         let mut  s1=String::from("hello");--->         let mut  s1=String::from("hell00");2994c2994<     a9f3:     ba 05 00 00 00          mov    $0x5,%edx--->     a9f3:     ba 06 00 00 00          mov    $0x6,%edx

也就是说从执行码也就是汇编的角度上看,只有执行mov    $0x6,%edx时,传递的参数一个是5一个是6,栈上的操作似乎只涉及长度len,这让我初步对于capacity这个值的存放位置产生了一定怀疑。

接下来我又用gdb调用了一下上面这个程序,其中print s1的结果如下

(gdb) print s1$2 = {vec = {buf = {ptr = {pointer = 0x5555557a0110 "hello\177",_marker = {<No data fields>}},cap = 5,alloc = {<No data fields>}},len = 5}}

在看到这个信息的时候,我想当然的以为cap是buf的一个item,而buf一般放在堆上,因此cap应该放在堆上,当时理解的图如下:

当然现在看这个结论的得出犯了想当然的经验主义错误,没有进行深入实证。

堆和栈到底是干嘛的

为了更好的向大家展示对于胖指针内存而已的验证方案,这里先简要介绍一下基本的汇编及gdb调试知识。

1.堆和栈:这里先来说一下运行时和编译时的概念,运行和编译其实是程序的两种时态,一些信息是程序运行之前就可以确定了,这种场景就对应编译时;另一类信息是程序真正运行起来才能确定的,这也就对应运行时。

一般来说栈用来对于分配编译时就可以确定的内存需求,比如某些运算任务我申请一些变量进行关联计算,这种场景下对于内存的需求在程序运行前就确定了,这种内存分配通过栈来解决就可以了;而堆则用来解决那些运行时才能确定的内存需求,其中最典型的就是字符串,由于字符串往往是由网络或者磁盘读出的,因此编译时无法确定其具体需求,这种情况下一般要通过堆分配内存。

栈的大小是提前确定的,比如我们在看汇编语言指令时函数的入口都是sub    $0x**,%rsp也就是进行栈的构建动作,示例如下:

000000000000aa00 <_ZN6hello14main17h5a48792de9598b5bE>:aa00:       48 81 ec 98 00 00 00    sub    $0x98,%rsplet mut  s1=String::from("hello");

而堆上的内存分配是操作系统malloc的产物,都是动态分配的,示例如下:

220a3:       ff 25 af 8c 22 00       jmpq   *0x228caf(%rip)        # 24ad58 <malloc@GLIBC_2.2.5>

因此栈的特点就是满足那些可以提前确定的编译时内存需求,并且程序员可以不去关心栈上内存的分配与释放,这些都是由编译器完成的工作。

而堆的特点则是满足运行时的内存需求,灵活性强,但是分配与释放都需要程序员人为管理。

2.Gdb调试方法简要说明:用gdb调试rust程序也很简单,只需要在编译时加上-g参数,然后用gdb启动调试就可以了,具体的指令如下:

rustc -g 文件名.rs
gdb 文件名

进入到gdb模式后,用list指令查看代码

(gdb) list1       fn main() {2               let mut  s1=String::from("hello");3               let len = calculate_length(&s1);4               println!("The Length is {}.",len);5            }6       fn calculate_length(s:&String)->usize{7       s.len()8       }9

使用b加行号设置断点,如

b 3

使用r命令运行程序

r

设置print的pretty参数为on

set print pretty on

查看栈寄存器信息

info reg rsp

打印变量信息

print s1

查看内存信息x/长度xb 内存地址如下:

X/5xb 0x5555557a0110

实锤证明胖指针的确胖在了栈上

说到这里其实相应的准备知识也就都有了。这里我们只需要进入到gdb去具体看一下情况就可以了。

1.确定栈空间位置:我们先按照上述gdb调试方法执行到第5步,确定rsp也就是栈顶的位置如下:

从构建栈的语句上看从栈顶向下0x98的范围内都是栈空间:

000000000000aa00 <_ZN6hello14main17h5a48792de9598b5bE>:
aa00:       48 81 ec 98 00 00 00    sub    $0x98,%rsp

确定胖指针中的ptr(指针)指向位置:接下来我们来看一下,变量s1的信息,得到了胖指针结构体中,指针指向的物理地址,并且这里还是要解释一下,初看cap属性和len属性的确不属于一个层级,这也是我一开始产生错误认识的原因。

确定ptr与字符串值 的实际对应关系:使用我们在上一节gdb调试的第7步命令,可以看到胖指针中ptr指向位置的内容分别对应”hello”的ascii码,因此可以确定指针指向堆上实际存放字符串的地址,这点没问题。

查看s1对象中ptr、len及cap属性的具体内存布局:我们刚刚已经确定了自栈顶(0x7fffffffe270)向下0x98范围内都属于栈空间,那么我们再通过x命令查看整个栈空间,具体注释如下:

可以看到通过gdb实际查看我们基本可以确定字符串s1的三个属性ptr,cap和len都是存在栈上的,而具体字符串的值则在堆上。之前cap存在堆上的想法自然也就是错的了。

极致挑错,胖指针内存到底如何内存布局

还有一点没有确定,上图中的例子,cap和len都是5,因此无法知道具体排列顺序关系,那么我们再来看以下代码:

fn main() {let mut s1 = String::new();s1.push_str("hello");println!("The length now is {}.",s1.len());println!("The cap now is {}.",s1.capacity());println!("Then addr now is {:p}.",s1.as_ptr());}

上述代码运行结果如下:

The length now is 5.

The cap now is 8.

Then addr now is 0x55afa3255110.

可以看到使用 s1.push_str的方法可能会使len与cap值不相同,那么这种情况下也就便于我们进行具体跟踪了。

实际观察内存布局时我们看到,cap属性与ptr是相领的,而非之前广为流传的图示中所说len与ptr相领,虽然这个错误不大,但是有关内存布局还是不能马虎,因此修改后正确的胖指针示意如下:

以上就是我对于Rust胖指针的学习理解过程,欢迎各位读者一如既往的提出意见,咱们共同进步!

马超,CSDN博客专家,阿里云MVP、华为云MVP,华为2020年技术社区开发者之星。

原文链接:https://blog.csdn.net/BEYONDMA/article/details/118460702

☞小米豪派“大红包”,人均 39 万元;约 200 家美国企业遭遇 REvil 勒索软件攻击;滴滴回应App被下架|极客头条☞私有化 Serverless Application 的探索与思考
☞两年盗取 1000 万美元的 Xbox 礼品卡,这个人竟然是“内鬼”!

从内存布局上看,Rust的胖指针到底胖在栈上还是堆上?相关推荐

  1. 台式计算机的美图,为什么我在台式电脑上看图片,图片色彩很饱和很鲜艳,而笔记本上看的图片有点暗淡呢...

    为什么我在台式电脑上看图片,图片色彩很饱和很鲜艳,而笔记本上看的图片有点暗淡呢以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下 ...

  2. 内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池

    先上代码,我们平时用的定时器,cadisplaylink.nstimer,CADisplayLink.NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 ...

  3. android手机8g内存够用嘛,安卓旗舰机8GB运行内存到底够不够用?有必要上12GB吗?...

    手机运行是否流畅,主要看三大方面,第一是处理器性能.第二是系统优化.第三就是运行内存了.或许运行内存对于苹果手机来说,影响不是特别大,毕竟三年前的iphone8,只有2GB运行内存,但放在今年运行还是 ...

  4. 理解Java对象:要从内存布局及底层机制说起,话说....

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上 ...

  5. JVM---对象的实例化内存布局与访问定位

    对象的实例化内存布局与访问定位 面试题 对象在JVM中是怎么存储的? 对象头信息里面有哪些东西? Java对象头有什么? 从对象创建的方式和步骤开始: 对象创建的方式 new:最常见的方式.单例类中调 ...

  6. java 对象压缩_理解Java对象:要从内存布局及底层机制说起,话说....

    前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上一篇文章中我们说了Java的类和对象在JVM中的存储方式,并使用HSDB进行佐证,没有看过上一篇文章的小伙伴可以点这里:< 这篇文章主要 ...

  7. 虚指针、虚表及内存布局

    一.虚指针及虚表的概念 首先要清楚,所谓指针其实质就是一个内存地址值,形如0x12345678: 其次,要知道,函数名本身就是一个地址: 虚指针:其实就是一个地址值,以该地址为起始地址的一片内存单元存 ...

  8. 【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  9. 计算机硬件是外观吗,计算机硬件从外观上看主要有主机箱.doc

    文档介绍: 计算机硬件从外观上看主要有主机箱.键盘和显示器;从逻辑功能上看,可以分为控制器.运算器.存储器.输入设备.输出设备五个部分,一般地又把运算器和控制器合称为中央处理器.判断一台计算机的性能主 ...

最新文章

  1. 西湖大学鞠峰:环境微生物宏基因组学(报告视频+PPT,11月23日)
  2. python 删除文件某一行
  3. 数据结构源码笔记(C语言):二叉平衡树的相关操作算法
  4. stonesoft 虚拟安全解决方案
  5. java读取pfx或P12格式的个人交换库公私钥
  6. c# 基于layui的通用后台管理系统_基于spring boot和vuejs的通用后台管理系统脚手架 guns-lite...
  7. 146. LRU Cache
  8. IBatisNet1.5学习--配置篇
  9. php输入对话框,如何使用JavaScript实现输入对话框
  10. Apache Mesos:编写您自己的分布式框架
  11. PHP+MySQL 跨服务器跨数据库数据拷贝系统
  12. 常见的排序算法二——希尔排序
  13. catia保存成stp文件时部件丢失_在线教学文件同步神器——坚果云
  14. 知乎7万赞回答:你思考问题的方式,决定了你的层次
  15. 舱机器人尾巴毛茸茸_『新奇玩意』毛茸茸的机器人不仅可撸,还会摇尾巴
  16. 只有一个文件的开源富文本编辑器,麻雀虽小五脏俱全就是它了
  17. 培养学生数学核心素养,不能制造“数学小糊涂”!
  18. php怎么定义字符串变量的值,php字符串变量怎么替换
  19. 如何下载福建省卫星地图高清版大图
  20. 最近想到的一些事情。

热门文章

  1. 剑指offer——22.链表中倒数第k个节点
  2. extjs 如何将局部的变量变为全局变量
  3. LNMP环境搭建笔记
  4. c# 鼠标控制图片大小
  5. 今天开博第一篇,呵呵
  6. 《Linux编程》学习笔记 ·004【文件I/O操作】
  7. [C] memset 初始化结果全为 -1
  8. mysql order by
  9. [模拟|数位] leetcode 7 整数反转
  10. php抓娃娃机器,vue制作抓娃娃机 - osc_icwhzig7的个人空间 - OSCHINA - 中文开源技术交流社区...