Swift系列产品七 - 选编剖析值种类

根据选编分下值种类的实质。

一、值种类

值种类取值给varlet或是给主要参数传参,是立即将全部內容复制一份。类似对文档开展拷贝实际操作,造成了全新升级的文档团本,归属于深拷贝(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))
}
/*
 輸出:
 c000007ffeefbff4c0
 c000007ffeefbff490
 */

打印出数据显示:p2p1的内存地址是不一样的,因此 改动p2不容易危害p1

1.2. 选编剖析(静态变量)

第一步:实例编码:

第二步:进到汇编代码后先搜索立即数:

第三步:进到p1的复位方式中:

第四步:继第三步finish后,再次返回以前的选编:

movq   %rax, -c010(%rbp)
movq   %rdx, -0x8(%rbp)
movq   %rax, -c020(%rbp)
movq   %rdx, -c018(%rbp)
movq   $c01e, -c020(%rbp)
movq   $c028, -c018(%rbp)

根据上边剖析得到:

  • p1的自变量x内存地址:rbp-c010

  • p1的自变量y内存地址:rbp-0x8

  • 且p1的2个自变量相距rbp-0x8-(rbp-c010) = 8个字节数;

  • p1的内存地址是rbp-c010

  • c01e取值给rbp-c020的详细地址,和上边的rax取值给rbp-c020是同一个详细地址,而且只是改动了一次。

因此 ,根据选编还可以强有力的证实值种类传送是深拷贝。

拓展:�i%esi是静态变量,未来发送给形参后会变为%rdi%rsi

1.3. 选编剖析(局部变量)

第一步:实例编码:

第二步:查询选编:

进到init方式发觉和上边的1.2分析基本一致,rdi给了raxrsi给了rdx

第三步:再次往前面看call以后的编码:

rip便是下一条命令的详细地址。
rax:10
rdx:20

c0100000ba4 < 52>:  movq   %rax, c0664d(%rip)
把rax给了详细地址:c0100000bab   c0664d = c01000071f8

c0100000bab < 59>:  movq   %rdx, c0664e(%rip) 
把rdx给了详细地址:0x100000bb2   c0664e = c0100007200

0x100000bb2 < 66>:  movq   %rcx, %rdi

观查发觉:rdx和rax恰好相距了c0100007200 - c01000071f8 = 八个字节数。

--------------------------------------------------------

0x100000bce < 94>:  movq   c06623(%rip), %rax
把详细地址 c0100000bd5   c06623 = 0x1000071f8 给了rax

c0100000bd5 < 101>: movq   %rax, c0662c(%rip)
把rax给了详细地址:0x100000bdc   c0662c = 0x100007208

0x100000bdc < 108>: movq   c0661d(%rip), %rax 
把详细地址 0x100000be3   c0661d = c0100007200 给了rax

0x100000be3 < 115>: movq   %rax, c06626(%rip)
把rax给了详细地址:0x100000bea   c06626 = c0100007210

0x100000bea < 122>: leaq   -c018(%rbp), %rdi

--------------------------------------------------------
观查发觉:
c01000071f8便是上边的10,c0100007200便是上边的20
就是,
把c01000071f8里边的值(10)取下来取值给了此外一块内存地址
0x100007208;
把c0100007200里边的值(20)取下来取值给了此外一块内存地址c0100007210
而且,
c0100007210和0x100007208相距八个字节数。

根据上边的剖析能够 得到,p1的内存地址就是0x1000071f8,p2的内存地址是0x100007208。还可以证实值种类是深拷贝。

工作经验:

  • 内存地址文件格式为:c0486f(%rip),一般是局部变量,全局性区(数据信息段);
  • 内存地址文件格式为:-0x8(%rbp),一般是静态变量,栈室内空间。
  • 内存地址文件格式为:c010(%rax),一般是堆室内空间。

规律性:

  • 局部变量代表着内存地址是固定不动的;
  • 静态变量的详细地址依靠rbp,而rbp右取决于rsprsp是外界传进去的(即调用函数)。

1.4. 取值实际操作

Swift标准库中,为了更好地提高特性,StringArrayDictionarySet采用了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]
 */

二、引用类型

引入取值给varlet或是给涵数传参,是将内存地址复制一份。

类似制做一个文档的配角(快捷方式图标),偏向的是同一个文档。归属于浅拷贝(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表针的内存地址: c000007ffeefbff478
 s1表针偏向的内存地址: 0x000000010061fe80
 s2表针的内存地址: c000007ffeefbff470
 s2表针偏向的内存地址: 0x000000010061fe80
 */

实例编码在运行内存中的主要表现:

思索: s2.width = 11; s2.height = 22,代码执行后,s1.widths1.height各自多少钱?

s2.width == 11, s2.height == 22,由于改动的是表针偏向的内存地址储存的数据信息,而s1s2偏向的是同一块内存。

2.2. 选编剖析

第一步:实例编码:

第二步:查询复位方式涵数的传参:

根据lldb命令获得rax的详细地址:

(lldb) register read rax
輸出:rax = 0x0000000100599840

再根据View Memory查询rax储存的数据信息有什么:

第三步:寻找p1p2

涵数详细地址rax给了静态变量-c010(%rbp),因此 -c010(%rbp)便是p1,同样-c028(%rbp)是p2。

第四步:查询s2widthheight是怎样被改动的:

  • 前边根据movq %rax, -c028(%rbp)把函数返回值rax给了-c028(%rbp)
  • 以后又根据movq -c028(%rbp), %rdx把函数返回值给了rdx
  • 历经(%rdx), %rsic068(%rsi), %rsi转站后,把rdx给了rsi
  • $c0b, �i实际上是把值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时,
建筑结构:

  • 建筑结构总体不可以被遮盖;
  • 建筑结构组员值也不可以改动。

引用类型:

  • 表针是不可以再次偏向新运行内存的。
  • 表针偏向的运行内存数据信息是能够 改动的。

评论(0条)

刀客源码 游客评论