iOS-Swift-汇编分析String、Array底层

一. 汇编分析String底层

iOS程序的内存布局

Mach-O文件是iOS的可执行文件,我们平时写的代码都在Mach-O,所以我们窥探Mach-O文件,就相当于窥探内存了(因为Mach-O文件载入内存不会有太大变化,只不过内存是动态更新的),如下图:

从编码到启动APP

问题1

1个String变量占用多少内存?
下面2个String变量,底层存储有什么不同?

var str1 = "0123456789" 
var str2 = "0123456789ABCDEF"

运行如下代码:

var str1 = "0123456789ABCDE"
print(MemoryLayout.stride(ofValue: str1)) //16 实际分配16字节
print(Mems.memStr(ofVal: &str1))

打印:

16
0x3736353433323130 0xef45444342413938

通过打印可知:
16:str1实际分配16字节,
0x3736353433323130 0xef45444342413938 :这是打印str1指针指向内存存储的东西,0x代表16进制,e代表直接把字符内容存储到内存中,f代表长度15,通过查询ASCII表可知0代表30,1代表31......等等

总结:
当字符串长度小于等于15,1个字节留着存放长度,另外15字节直接存放字符串的ASCII值,类似于OC的tagger pointer技术

如果字符串再多一位呢?

var str2 = "0123456789ABCDEF"
print(MemoryLayout.stride(ofValue: str2)) //16 实际分配16
print(Mems.memStr(ofVal: &str2))

打印:

16
0xd000000000000010 0x800000010000a790

可以发现,当字符串长度大于15,就不是直接存储字符串的值了

MJ老师通过窥探汇编得出如下结果,对于0xd000000000000010 0x800000010000a790:

  1. 前8字节存放的是字符创的长度10,在16进制中就是16
  2. 后8个字节存放的是:0x800000010000a790 = 字符串的真实地址 + 0x7fffffffffffffe0,所以字符串的真实地址 = 0x800000010000a790 - 0x7fffffffffffffe0(小技巧:字符串的真实地址 = 0x000000010000a790 + 0x20)

通过计算得出0x10000A7B0是"0123456789ABCDEF"的真实地址,读取这个地址的内存,如下:

x 0x10000a7b0: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46   0123456789ABCDEF

可以发现,这个内存地址存储的的确是str2字符串。

  • 使用MachoView

那个这个地址:0x10000A7B0在哪呢?
这就要用到文章开头的知识了,我们窥探Mach-O文件,就相当于窥探内存,在Mach-O文件中我们可以知道0x10000A7B0地址存放在哪里

补充:
0x100000000: VM Address 虚拟地址
0x10000A7B0 = 0x100000000 + 0xA7B0
0xA7B0是Mach文件的地址,所以我们在Mach-O文件中找0xA7800就好了

运行程序,使用MachoView打开程序的可执行文件,可以发现0xA7B0放在常量区,如下:

Mach-O文件.png

总结:
字符串长度 <= 0xF(15),字符串内容直接存放在str1变量的内存中(比如:var str1 = "0123456789")
字符串长度 > 0xF(15),字符串内容存放在__TEXT.cstring中(常量区
字符串的地址值信息存放在str2变量的后8个字节中(比如:var str2 = "0123456789ABCDEFGHIJ")

问题2

如果对String进行拼接操作,String变量的存储会发生什么变化?

var str1 = "0123456789" 
var str2 = "0123456789ABCDEF"
str1.append("ABCDE") 
str1.append("F")
str2.append("G")

运行如下代码:

var str1 = "01234567"
print(Mems.memStr(ofVal: &str1))
str1.append("GIHJ")
print(Mems.memStr(ofVal: &str1))

打印:

0x3736353433323130 0xe800000000000000
0x3736353433323130 0xec0000004a484947

可以发现,当字符串进行拼接的时候,如果拼接后字符串长度还是小于等于15,那么拼接的字符串还会放在原来的后面

如果字符串长度本来是16,拼接后大于16呢?

var str2 = "0123456789ABCDEF"
print(Mems.memStr(ofVal: &str2))
str2.append("G")
print(Mems.memStr(ofVal: &str2))

打印:

0xd000000000000010 0x8000000100006620  //16字节,放常量区
0xf000000000000011 0x000000010068af60  //大于16字节,放堆空间

根据上面的小技巧,0x000000010068af60 + 0x20得到字符串的真实地址:0x000000010068af80,查看真实地址内存,发现存储的的确是上面的字符串

(lldb) x 0x000000010068af80
0x10068af80: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46  0123456789ABCDEF
0x10068af90: 47 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  G...............
(lldb)

如何证明上面str2.append("G")之后是存放在堆空间?
很简单,在malloc函数打个断点,如果走malloc函数,就说明在堆空间
MJ老师在汇编里面在malloc函数打个断点,执行str2.append("G")发现的确走了malloc,说明str2.append("G")之后是存放在堆空间

大总结:

现在可以回答开头两个问题了

var str1 = "0123456789"
字符串长度 <= 0xF(15),字符串内容直接存放在str1变量的内存中

var str2 = "0123456789ABCDEF"
字符串长度 > 0xF(15),字符串内容存放在__TEXT.cstring中(常量区)
字符串的地址值信息存放在str2变量的后8个字节中

str1.append("ABCDE")
由于字符串长度 <= 0xF,所以字符串内容依然存放在str1变量的内存中

str1.append("F")
开辟堆空间
可能你会疑问这里为什么是开辟堆空间?
拼接之前str1是0123456789ABCDE,这时候是字符串15字节+1字节(存放长度),16个字节已经满了,所以无法拼接。
那么放常量区呢?更不可以,因为常量区的内容不可以改,所以只能开辟堆空间。

str2.append("G")
开辟堆空间

二. 汇编分析Array底层

关于Array的思考

public struct Array 
var arr = [1, 2, 3, 4]

1个Array变量占用多少内存?
数组中的数据存放在哪里?

Array变量存放哪里

你可能感兴趣的:(iOS-Swift-汇编分析String、Array底层)