站在汇编角度深入了解 Swift(十三)

2020/4/7 23:01:33

本文主要是介绍站在汇编角度深入了解 Swift(十三),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

关于 String 的思考

  1. 1个 String 变量占用多少内存?
  2. 下面2个 String 变量,底层存储有什么不同?
var str1 = "0123456789"
var str2 = "0123456789012345"
复制代码
  1. 如果对 String 进行拼接操作,String 变量的存储会发生什么变化?
str1.append("01234")
str1.append("5")
str2.append("6")
str2.append("7")
-------------------执行结果-------------------
(lldb) x/5wg 0x7ffeefbff420
0x7ffeefbff420: 0x3736353433323130 0xea00000000003938
0x7ffeefbff430: 0x0000000000000000 0x0000000000000000
0x7ffeefbff440: 0x00007ffeefbff460
(lldb) x/5wg 0x7ffeefbff420
0x7ffeefbff420: 0x3736353433323130 0xef34333231303938
0x7ffeefbff430: 0x0000000000000000 0x0000000000000000
0x7ffeefbff440: 0x00007ffeefbff460
(lldb) x/5wg 0x7ffeefbff420
0x7ffeefbff420: 0xf000000000000010 0x000000010073f900

(lldb) x/5wg 0x7ffeefbff410
0x7ffeefbff410: 0xd000000000000010 0x8000000100014890
0x7ffeefbff420: 0xf000000000000010 0x000000010280e670
0x7ffeefbff430: 0x0000000000000000
(lldb) x/5wg 0x7ffeefbff410
0x7ffeefbff410: 0xf000000000000011 0x000000010280f1a0
// rdx = 0x000000010280f1a0 + 0x8000000000000020
// rdx = 0x800000010280f1c0
(lldb) x/5wg 0x000000010280f1c0
0x10280f1c0: 0x3736353433323130 0x3534333231303938
0x10280f1d0: 0x6f6e206567610036 0x0000000000000000
复制代码

汇编探究

var str1 = "0123456789"
var str2 = "0123456789012345"
printMemory(t1: str1)
printMemory(t1: str2)
---------------------执行结果---------------------
16
16
8
16
16
8
复制代码

所以第一题的答案是16个字节

small string

  • 类似于 OC 中的 taggedPointer
  • 地址存储的就是值
var str2 = "0123456789"
------------------执行结果-----------------
0x7ffeefbff420
0x7ffeefbff420: 0x3736353433323130 0xea00000000003938
复制代码

可以看出来在内存中存储的是字符串的值,后面字节存储的是字符串的升序

string

swiftstudy`test46():
// rdi = 0x00000001000148c0  "0123456789012345"
// 0x1000148c0: 0x3736353433323130 0x3534333231303938
// 0x1000148d0: 0x0000000000000000 0x0000000000000000
// rdi 存储的是字符串的真实地址,可以看出来是放在全局区的
leaq   0xd08a(%rip), %rdi        ; "0123456789012345"
// 字符串的长度
movl   $0x10, %esi
callq  0x100012180               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
// rax: 0xd000000000000010
movq   %rax, -0x10(%rbp)
// rdx: 0x80000001000148a0
movq   %rdx, -0x8(%rbp)

libdyld.dylib`dyld_stub_binder:

libswiftCore.dylib`Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String:
// 比较字符串的长度和0xf的大小,如果小于 15,就采用 small string 的方式,否则就按普通的字符串存储
cmpq   $0xf, %rsi
jle    0x7fff6d40ecab
// rdx = 0x7fffffffffffffe0
// rdx = 0x8000000000000020
movabsq $0x7fffffffffffffe0, %rdx
// rdx = rdx + rdi
addq   %rdx, %rdi
复制代码

ASCII码表

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 中,开辟堆开间

思考

  1. 如何证明 rdi 存储的地址就放在全局区呢?

    用 MachOView

  2. dyld_stub_binder 的懒加载是怎么实现的?

第一次调用
// 0x100017038
jmpq   *0x4ed2(%rip)             ; (void *)0x00000001000123a0
复制代码

MachOView
可以看出来上面的函数地址和我们平时自己写的函数地址不是存在同一个区域的,我们平时都是在 __TEXT 区,他是存在 __DATA 区域的,也就是全局区,是允许我们在程序运行过程中动态修改的,当我们绑定完一次 dyld_stub_binder 后,程序会帮我们把这块内存修改了能够真正调用的地址了。

第二次调用
jmpq   *0x4ed2(%rip)             ; (void *)0x00007fff6d40ec40:
复制代码

关于 Array 的思考

  1. 1个 Array 变量占多少内存?
var a = [1, 2, 3, 4]
printMemory(t1: a)
print(malloc_size(UnsafeRawPointer(bitPattern: unsafeBitCast(a, to: Int.self))))
-----------------------执行结果-----------------------
8
8
8
64
复制代码

占8个字节 2. 数组中的数据存放在哪里?

-----------------------分析-----------------------
(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
复制代码

数组的内存结构

思考

  1. Array 底层更接近于 Class,为什么用结构体来实现?
  2. 数组的容量是什么时机扩容的?
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
复制代码

感觉像是预设了几个数组的大小,当你的容量达到一组上限的一半的时候,就会扩容,使用另一个更大的数字

Array 拼接后会发生什么



这篇关于站在汇编角度深入了解 Swift(十三)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程