美文网首页
iOS-Swift-汇编分析String、Array底层

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

作者: Imkata | 来源:发表于2020-01-15 16:09 被阅读0次

    一. 汇编分析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<Element> 
    var arr = [1, 2, 3, 4]
    

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

    Array变量存放哪里

    相关文章

      网友评论

          本文标题:iOS-Swift-汇编分析String、Array底层

          本文链接:https://www.haomeiwen.com/subject/fbhaactx.html