Swift系列七 - 汇编分析值类型
通过汇编分下值类型的本质。
一、值类型
值类型赋值给var,let或者给参数传参,是直接将所有内容拷贝一份。类似于对文件进行复制粘贴操作,产生了全新的文件副本,属于深拷贝(deep copy)。
示例:
func testStruct() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
print("before:p1.x:\(p1.x),p1.y:\(p1.y)")
var p2 = p1
print("before:p2.x:\(p2.x),p2.y:\(p2.y)")
p2.x = 30
p2.y = 40
print("after:p1.x:\(p1.x),p1.y:\(p1.y)")
print("after:p2.x:\(p2.x),p2.y:\(p2.y)")
}
/*
输出:
before:p1.x:10,p1.y:20
before:p2.x:10,p2.y:20
after:p1.x:10,p1.y:20
after:p2.x:30,p2.y:40
*/
通过上面的示例可以看出,给p2重新赋值确实没有影响到p1的值。
1.1. 内存分析
我们也可以通过内存看下上面示例中变量地址是否发生改变,如果生成了新的地址值,则说明是深拷贝。
func testStruct() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
print(Mems.ptr(ofVal: &p1))
print(Mems.ptr(ofVal: &p2))
}
/*
输出:
0x00007ffeefbff4c0
0x00007ffeefbff490
*/
打印结果显示:p2和p1的内存地址是不同的,所以修改p2不会影响p1。
1.2. 汇编分析(局部变量)
第一步:示例代码:

第二步:进入汇编代码后先查找立即数:

第三步:进入p1的初始化方法中:

第四步:继第三步finish后,继续回到之前的汇编:

movq %rax, -0x10(%rbp)
movq %rdx, -0x8(%rbp)
movq %rax, -0x20(%rbp)
movq %rdx, -0x18(%rbp)
movq $0x1e, -0x20(%rbp)
movq $0x28, -0x18(%rbp)
通过上面分析得出:
p1的变量x内存地址:
rbp-0x10;p1的变量y内存地址:
rbp-0x8;且p1的两个变量相差
rbp-0x8-(rbp-0x10) = 8个字节;p1的内存地址是
rbp-0x10。0x1e赋值给rbp-0x20的地址,和上面的rax赋值给rbp-0x20是同一个地址,并且仅仅修改了一次。
所以,通过汇编也可以有力的证明值类型传递是深拷贝。
扩展:
%edi和%esi是局部变量,将来传给形参后会变成%rdi和%rsi。
1.3. 汇编分析(全局变量)
第一步:示例代码:

第二步:查看汇编:

进入init方法发现和上面的1.2分析基本一致,rdi给了rax,rsi给了rdx:

第三步:继续往后面看call之后的代码:

rip就是下一条指令的地址。
rax:10
rdx:20
0x100000ba4 <+52>: movq %rax, 0x664d(%rip)
把rax给了地址:0x100000bab + 0x664d = 0x1000071f8
0x100000bab <+59>: movq %rdx, 0x664e(%rip)
把rdx给了地址:0x100000bb2 + 0x664e = 0x100007200
0x100000bb2 <+66>: movq %rcx, %rdi
观察发现:rdx和rax刚好相差了0x100007200 - 0x1000071f8 = 8个字节。
--------------------------------------------------------
0x100000bce <+94>: movq 0x6623(%rip), %rax
把地址 0x100000bd5 + 0x6623 = 0x1000071f8 给了rax
0x100000bd5 <+101>: movq %rax, 0x662c(%rip)
把rax给了地址:0x100000bdc + 0x662c = 0x100007208
0x100000bdc <+108>: movq 0x661d(%rip), %rax
把地址 0x100000be3 + 0x661d = 0x100007200 给了rax
0x100000be3 <+115>: movq %rax, 0x6626(%rip)
把rax给了地址:0x100000bea + 0x6626 = 0x100007210
0x100000bea <+122>: leaq -0x18(%rbp), %rdi
--------------------------------------------------------
观察发现:
0x1000071f8就是上面的10,0x100007200就是上面的20
就是说,
把0x1000071f8里面的值(10)取出来赋值给了另外一块内存地址
0x100007208;
把0x100007200里面的值(20)取出来赋值给了另外一块内存地址0x100007210
并且,
0x100007210和0x100007208相差8个字节。
通过上面的分析可以得出,p1的内存地址就是0x1000071f8,p2的内存地址是0x100007208。也可以证明值类型是深拷贝。
经验:
- 内存地址格式为:
0x486f(%rip),一般是全局变量,全局区(数据段); - 内存地址格式为:
-0x8(%rbp),一般是局部变量,栈空间。 - 内存地址格式为:
0x10(%rax),一般是堆空间。
规律:
- 全局变量意味着内存地址是固定的;
- 局部变量的地址依赖
rbp,而rbp右依赖于rsp,rsp是外部传进来的(即函数调用)。
1.4. 赋值操作
在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术。
Copy On Write: 当需要进行内存操作(写)时,才会进行深度拷贝。
对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证最佳性能来避免赋值。
建议:不需要修改的,尽量定义为
let。
1.4.1. 示例代码一(字符串):
var str1 = "idbeny"
var str2 = str1
str2.append("1024星球")
print(str1)
print(str2)
/*
输出:
idbeny
idbeny1024星球
*/
1.4.2. 示例代码二(数组):
var arr1 = ["1", "2", "3"]
var arr2 = arr1
arr2.append("4")
arr1[0] = "one"
print(arr1)
print(arr2)
/*
输出:
["one", "2", "3"]
["1", "2", "3", "4"]
*/
1.4.3. 示例代码三(字典):
var dict1 = ["name": "大奔", "age": 20] as [String : Any]
var dict2 = dict1
dict1["name"] = "idbeny"
dict2["age"] = 30
print(dict1)
print(dict2)
/*
输出:
["name": "idbeny", "age": 20]
["name": "大奔", "age": 30]
*/
二、引用类型
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份。
类似于制作一个文件的替身(快捷方式),指向的是同一个文件。属于浅拷贝(shallow copy)。
2.1. 内存分析
示例代码:
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
func test() {
var s1 = Size(width: 10, height: 20)
var s2 = s1
print("s1指针的内存地址:",Mems.ptr(ofVal: &s1))
print("s1指针指向的内存地址:",Mems.ptr(ofRef: s1))
print("s2指针的内存地址:",Mems.ptr(ofVal: &s2))
print("s2指针指向的内存地址:",Mems.ptr(ofRef: s2))
}
test()
/*
输出:
s1指针的内存地址: 0x00007ffeefbff478
s1指针指向的内存地址: 0x000000010061fe80
s2指针的内存地址: 0x00007ffeefbff470
s2指针指向的内存地址: 0x000000010061fe80
*/
示例代码在内存中的表现:

思考:
s2.width = 11; s2.height = 22,代码执行后,s1.width和s1.height分别是多少?
s2.width == 11, s2.height == 22,因为修改的是指针指向的内存地址保存的数据,而s1和s2指向的是同一块内存。
2.2. 汇编分析
第一步:示例代码:

第二步:查看初始化方法函数的返回值:

通过lldb指令得到rax的地址:
(lldb) register read rax
输出:rax = 0x0000000100599840
再通过View Memory查看rax保存的数据有哪些:

第三步:找到p1和p2:

函数地址rax给了局部变量-0x10(%rbp),所以-0x10(%rbp)就是p1,同理-0x28(%rbp)是p2。
第四步:查看s2的width和height是如何被修改的:

- 前面通过
movq %rax, -0x28(%rbp)把函数返回值rax给了-0x28(%rbp); - 之后又通过
movq -0x28(%rbp), %rdx把函数返回值给了rdx; - 经过
(%rdx), %rsi和0x68(%rsi), %rsi中转后,把rdx给了rsi; $0xb, %edi其实是把值11给了edi(即rdx)。
所以,width和height其实修改的是同一块内存地址。
2.3. 赋值操作
示例代码:
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
s1 = Size(width: 11, height: 22)
在内存中的表现:

s1刚开始指向堆空间02,后又指向堆空间01。当堆空间02没有强指针指向时就会被销毁。
三、值类型、引用类型的let

使用let时,
结构体:
- 结构体整体不能被覆盖;
- 结构体成员值也不能修改。
引用类型:
- 指针是不能重新指向新内存的。
- 指针指向的内存数据是可以修改的。

Swift系列七 - 汇编分析值类型的更多相关文章
- Swift的字符串String是值类型
根据<The Swift Programming Language>中文版基于Xcode6.1的文章描述: Swift的 String 类型是值类型.如果创建了新的字符串,那么当其进行常量 ...
- 深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别
C#初学者经常被问的几道辨析题,值类型与引用类型,装箱与拆箱,堆栈,这几个概念组合之间区别,看完此篇应该可以解惑. 俗话说,用思想编程的是文艺程序猿,用经验编程的是普通程序猿,用复制粘贴编程的是2B程 ...
- c#基础系列1---深入理解值类型和引用类型
"大菜":源于自己刚踏入猿途混沌拾起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识, ...
- C#进阶系列——WebApi接口返回值类型详解
阅读目录 一.void无返回值 二.IHttpActionResult 1.Json(T content) 2.Ok(). Ok(T content) 3.NotFound() 4.其他 5.自定义I ...
- Storm系列(七)架构分析之Scheduler-调度器[DefaultScheduler]
Storm默认的任务调度器.实现如下: 1 (defn –prepare [this conf]) 2 (defn –schedule [this ^Topologies topologies ^ ...
- Swift 值类型和引用类型的内存管理
1.内存分配 1.1 值类型的内存分配 在 Swift 中定长的值类型都是保存在栈上的,操作时不会涉及堆上的内存.变长的值类型(字符串.集合类型是可变长度的值类型)会分配堆内存. 这相当于一个 &qu ...
- swift的值类型和引用类型
前言 最近在学设计模式中,发现 Swift 中的 struct,class 以及 enum 在一般的使用中能够做到互相替换,因此探究其背后的逻辑就十分有必要.而这一问题又引出了 Swift 中的值类型 ...
- 从CLR角度来看值类型与引用类型
前言 本文中大部分示例代码来自于<CLR via C# Edition3>,并在此之上加以总结和简化,文中只是重点介绍几个比较有共性的问题,对一些细节不会做过深入的讲解. 前几天一直忙着翻 ...
- c# 值类型和引用类型 笔记
参考以下博文,我这里只是笔记一下,原文会更加详细 c#基础系列1---深入理解值类型和引用类型 堆栈和托管堆c# 值类型和引用类型 红色表示——“这啥?”(真实1个问题引出3个问题) CLR支持的两种 ...
随机推荐
- 单链表c语言实现的形式
包括初始化,创建,查询,长度,删除,清空,销毁等操作 代码如下: #include<stdio.h> #include<stdlib.h> //定义单链表的数据类型 typed ...
- [GDKOI2021] 普及组 Day2 总结
[ G D K O I 2021 ] 普 及 组 D a y 2 总 结 [GDKOI2021] 普及组 Day2 总结 [GDKOI2021]普及组Day2总结 时间安排和昨天的GDKOI2021 ...
- 锋利的NodeJS之NodeJS多线程
最近刚好有朋友在问Node.js多线程的问题,我总结了一下,可以考虑使用源码包里面的worker_threads或者第三方的模块来实现. 首先明确一下多线程在Node.js中的概念,然后在聊聊work ...
- kestrel Server的源码分析
今天这一篇博客讲的是.net core 自带的kestrel server,当你开发微服务k8s部署在linux环境下,一般默认开启这个高性能服务,如果大家之前看过我的owin katana的博客,会 ...
- 自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像上传-11
自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像上传-11 欢迎加QQ群:1026880196 进行交流学习 镜像上传 #controller1 ...
- 诸葛亮的锦囊妙计竟然是大名鼎鼎的Java设计模式:策略模式
目录 应用场景 简单实现例子 改进代码 策略模式 定义 意图 主要解决问题 何时使用 优缺点 诸葛亮的锦囊妙计 应用场景 京东.天猫双十一,情人节商品大促销,各种商品有不同的促销活动 满减:满200减 ...
- Day17_104_IO_BufferReader接收用户输入
BufferReader接收用户输入 **标准输入\输出流 java.lang.System** - static InputStream in - static PrintStream out im ...
- Day14_76_反射与静态语句块
反射与静态语句块 * 获取class对象与静态语句块的关系 package com.shige.Reflect; import java.nio.channels.ClosedSelectorExce ...
- 测试报告模板:HTMLTestRunner.py(新版)
报告样式效果: 报告源码:HTMLTestRunner.py 1 """ 2 A TestRunner for use with the Python unit test ...
- 02- HTML网页基础知识与浏览器介绍
1.认识网页 网页主要由文字,图像和超链接等元素构成.当然,除了这些元素,网页还可以包含音频,视频,以及flask等. 如图所示就是一个网页: 网页是如何形成的呢? 它是由前端人员写的代码,经过浏览器 ...