汇编分析String、Array
String
几个关于String的问题
- 1个String变量占用度多少内存
- 下面2个String变量,底层村村有什么不同?
var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
-
如果对String进行拼接操作, String变量的储存会发生什么变化?
str1.append("ABFC") -
问题回答
- 字符串长度 <= 0xf, 字符串内容直接存放在str1变量的内存中
- 拼接字符串时,如果还是 字符串长度 <= 0xf 字符串内容还是直接存放在str1变量的内存中
- 字符串长度 > 0xf , 字符串内容存放在__TEXT.cstring中(常量区)
- 字符串的地址信息存放砸死str2变量的后8个字节中
- 拼接字符串时, 常量区不能更改, 所以开辟堆空间
- 字符串长度 <= 0xf, 字符串内容直接存放在str1变量的内存中
var str1 = "123456789"
print(Mems.memStr(ofVal: &str1))
str1.append("G")
print(Mems.memStr(ofVal: &str1)) // 0x3837363534333231 0xea00000000004739
// 0x3837363534333231 0xe900000000000039 就是ASCII值 0xe 类型标识 9是长度 类似 OC的 Targe Point
// 最长为f 15个字节
// 如果长度小于等于15 还是放在了本身的16个字节里
var str2 = "123456789ABCDEFG" // 大于15位
print(Mems.memStr(ofVal: &str2))
str2.append("G")
print(Mems.memStr(ofVal: &str2))
/* 0xd000000000000010 0x80000001000056e0
0xd000000000000010 10 字符串的长度 0xd 是标志位
0x80000001000056e0 决定字符串的真实地址
字符串的真实地址 = 0x80000001000056e0 - 0x7fffffffffffffe0
字符串的真实地址 = 0x00000001000056e0 + 0x20
大于15位 调用 String.init函数 比较字符串长度 是否大于15
movabsq $0x7fffffffffffffe0,
%rdx %rdi 存放着字符串的真实地址
%rsi 存放的是字符串的长度0x10
%rdx 存放的是字符串的真实地址 + 0x7fffffffffffffe0
callq String.init 的两个参数 rdi rsi
在Text_cstring 段 代码段 */
/*
拼接后 0xf000000000000011 0x0000000100636230
重新分配了堆空间
因为在代码段 所以那边是不能改的
0xf0 标志位 依据上面的方法 能得到真实的地址值 储存在堆空间
0x0000000100636230 是堆空间的地址值 跳过0x20 也就是32个字节 就是存放着字符串的真实内容
*/
从编码到启动App

libdyld.dylib`dyld_stub_binder
- 符号的延迟绑定通过 dyld_stub_binder 完成(符号绑定)
- jmpq *0x1bc9(%rip) 占用六个字节
var str1 = "0123456789"
var str2 = "0123456789"
-----------------汇编分析--------------------
第一次:
jmpq *0x4ed2(%rip) ; (void *)0x00000001000123a0
jmpq *0x3e35(%rip) ; (void *)0x00007fff6df9c12c: dyld_stub_binder
然后调用 dyld_stub_binder 中就可以进行绑定了
第二次:
jmpq *0x4ed2(%rip) ; (void *)0x00007fff6d40ec40: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
因为我们是调用系统库的函数,我们自己并没有实现它,所以需要用这个函数帮我们把这个函数和系统的绑定到一起,这个是懒加载,也就是说当用到了之后他才会去绑定,而且绑定一次之后也会缓存起来,下次就不用再次绑定了,这个里面的汇编基本可以跳过。
总结
- 拼接前的字符串
- 字符串长度小于等于 0xF,字符串内容直接存放在 str1 变量的内存中
- 字符串长度大于 0xF,字符串内容存放在 __TEXT.cstring 中(常量区)
- 因为这个是编译的时候就确定的,所以不管有多长都会存放在这个区域
- 字符串的地址值信息存放在 str2 变量的后8个字节,不过需要经过一些计算就可以得到真正存储的地址了
- 后8个字节 + 0x0000000000000020 就可以拿到真正的地址了
- 拼接后的字符串
- 接拼后的字符串长度小于等于 0xF,所以字符串内容依然存放在 str1 变量的内存中
- 如果拼接后的字符串长充大于 0xF 中,开辟堆空间
思考
- 如何证明 rdi 存储的地址就放在全局区呢?
用 MachOView - dyld_stub_binder 的懒加载是怎么实现的?
第一次调用
// 0x100017038
jmpq *0x4ed2(%rip) ; (void *)0x00000001000123a0

可以看出来上面的函数地址和我们平时自己写的函数地址不是存在同一个区域的,我们平时都是在 __TEXT 区,他是存在 __DATA 区域的,也就是全局区,是允许我们在程序运行过程中动态修改的,当我们绑定完一次 dyld_stub_binder 后,程序会帮我们把这块内存修改了能够真正调用的地址了
第二次调用
jmpq *0x4ed2(%rip) ; (void *)0x00007fff6d40ec40:
Array
关于 Array 的思考
- 1个 Array 变量占多少内存?
占用了8个字节,存储的是地址信息,指向数组的数据
var a = [1, 2, 3, 4]
print(Memory(t1: a))
print(malloc_size(UnsafeRawPointer(bitPattern: unsafeBitCast(a, to: Int.self))))
-----------------------执行结果-----------------------
8
8
8
64
- 数组中的数据存放在哪里?
-----------------------分析-----------------------
(lldb) x/5wg 0x7ffeefbff438
0x7ffeefbff438: 0x000000010072f4a0 0x00007ffeefbff460
0x7ffeefbff448: 0x0000000100000c34 0x00007ffeefbff480
0x7ffeefbff458: 0x000000010004c025
(lldb) x/10wg 0x000000010072f4a0
0x10072f4a0: 0x00007fff97d19260 0x0000000000000002
0x10072f4b0: 0x0000000000000004 0x0000000000000008
0x10072f4c0: 0x0000000000000001 0x0000000000000002
0x10072f4d0: 0x0000000000000003 0x0000000000000004
0x10072f4e0: 0x0000000000000000 0x0000000000000000

思考
- Array 底层更接近于 Class,为什么用结构体来实现?
因为它的行为一般是值类型,当做值类型用 - 数组的容量是什么时机扩容的?
var a = [Int]()
(0...15).forEach { (i) in
a.append(i)
}
(lldb) x/5wg 0x000000010057f090
0x10057f090: 0x00007fff97d19f80 0x0000000000000002
0x10057f0a0: 0x0000000000000010 0x0000000000000020
var a = [Int]()
(0...16).forEach { (i) in
a.append(i)
}
(lldb) x/5wg 0x000000010057f090
0x10057f090: 0x00007fff97d19f80 0x0000000000000002
0x10057f0a0: 0x0000000000000011 0x0000000000000040
var a = [Int]()
(0...64).forEach { (i) in
a.append(i)
}
(lldb) x/100wg 0x0000000104000000
0x104000000: 0x00007fff97d19f80 0x0000000000000002
0x104000010: 0x0000000000000041 0x0000000000000178
0x104000020: 0x0000000000000000 0x0000000000000001
0x104000030: 0x0000000000000002 0x0000000000000003
0x104000040: 0x0000000000000004 0x0000000000000005
0x104000050: 0x0000000000000006 0x0000000000000007
0x104000060: 0x0000000000000008 0x0000000000000009
0x104000070: 0x000000000000000a 0x000000000000000b
0x104000080: 0x000000000000000c 0x000000000000000d
0x104000090: 0x000000000000000e 0x000000000000000f
0x1040000a0: 0x0000000000000010 0x0000000000000011
0x1040000b0: 0x0000000000000012 0x0000000000000013
0x1040000c0: 0x0000000000000014 0x0000000000000015
0x1040000d0: 0x0000000000000016 0x0000000000000017
0x1040000e0: 0x0000000000000018 0x0000000000000019
0x1040000f0: 0x000000000000001a 0x000000000000001b
0x104000100: 0x000000000000001c 0x000000000000001d
0x104000110: 0x000000000000001e 0x000000000000001f
0x104000120: 0x0000000000000020 0x0000000000000021
0x104000130: 0x0000000000000022 0x0000000000000023
0x104000140: 0x0000000000000024 0x0000000000000025
0x104000150: 0x0000000000000026 0x0000000000000027
0x104000160: 0x0000000000000028 0x0000000000000029
0x104000170: 0x000000000000002a 0x000000000000002b
0x104000180: 0x000000000000002c 0x000000000000002d
0x104000190: 0x000000000000002e 0x000000000000002f
0x1040001a0: 0x0000000000000030 0x0000000000000031
0x1040001b0: 0x0000000000000032 0x0000000000000033
0x1040001c0: 0x0000000000000034 0x0000000000000035
0x1040001d0: 0x0000000000000036 0x0000000000000037
0x1040001e0: 0x0000000000000038 0x0000000000000039
0x1040001f0: 0x000000000000003a 0x000000000000003b
0x104000200: 0x000000000000003c 0x000000000000003d
0x104000210: 0x000000000000003e 0x000000000000003f
0x104000220: 0x0000000000000040 0x00007fff8eeca970
0x104000230: 0x00007fff8eeca988 0x00007fff8eeca9a0
0x104000240: 0x00007fff8eeca9b8 0x00007fff8eeca9d0
感觉像是预设了几个数组的大小,当你的容量达到一组上限的一半的时候,就会扩容,使用另一个更大的数字

浙公网安备 33010602011771号