iOS开发-Swift进阶之值类型 & 引用类型!

本文主要介绍为什么结构体是值类型,类是引用类型

iOS开发-Swift进阶之值类型 & 引用类型!


值类型

前提:需要了解内存五大区,,如下所示

iOS开发-Swift进阶之值类型 & 引用类型!

  • 栈区的地址 比 堆区的地址 大

  • 栈是从高地址->低地址,向下延伸,由系统自动管理,是一片连续的内存空间

  • 堆是从低地址->高地址,向上延伸,由程序员管理,堆空间结构类似于链表,是不连续的

  • 日常开发中的溢出是指堆栈溢出,可以理解为栈区与堆区边界碰撞的情况

  • 全局区、常量区都存储在Mach-O中的__TEXT cString

我们通过一个例子来引入什么是值类型

func test(){
    //栈区声明一个地址,用来存储age变量
    var age = 18
    //传递的值
    var age2 = age
    //age、age2是修改独立内存中的值
    age = 30
    age2 = 45

    print("age=\(age),age2=\(age2)")
}
test()

从例子中可以得出,age存储在栈区

  • 查看age的内存情况,从图中可以看出,栈区直接存储的是
    • 获取age的栈区地址:po withUnsafePointer(to: &age){print($0)}
    • 查看age内存情况:x/8g 0x00007ffeefbff3e0

iOS开发-Swift进阶之值类型 & 引用类型!

  • 查看age2的情况,从下图中可以看出,age2的赋值相当于将age中的值拿出来,赋值给了age2。其中ageage2 的地址 相差了8字节,从这里可以说明栈空间是连续的、且是从高到低

iOS开发-Swift进阶之值类型 & 引用类型!

所以,从上面可以说明,age就是值类型

值类型 特点

  • 1、地址中存储的是

  • 2、值类型的传递过程中,相当于传递了一个副本,也就是所谓的深拷贝

  • 3、值传递过程中,并不共享状态

结构体

结构体的常用写法

//***** 写法一 *****
struct CJLTeacher {
    var age: Int = 18

    func teach(){
        print("teach")
    }
}
var t = CJLTeacher()

//***** 写法二 *****
struct CJLTeacher {
    var age: Int

    func teach(){
        print("teach")
    }
}
var t = CJLTeacher(age: 18)
  • 在结构体中,如果不给属性默认值,编译是不会报错的。即在结构体中属性可以赋值,也可以不赋值

    iOS开发-Swift进阶之值类型 & 引用类型!

  • init方法可以重写,也可以使用系统默认的

结构体的SIL分析

  • 如果没有init,系统会提供不同的默认初始化方法

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果提供了自定义的init,就只有自定义的

    iOS开发-Swift进阶之值类型 & 引用类型!

为什么结构体是值类型?

定义一个结构体,并进行分析

struct CJLTeacher {
    var age: Int = 18
    var age2: Int = 20
}
var  t = CJLTeacher()
print("end")
  • 打印t:po t,从下图中可以发现,t的打印直接就是值,没有任何与地址有关的信息

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 获取t的内存地址,并查看其内存情况

    • 获取地址:po withUnsafePointer(to: &t){print($0)}

    • 查看内存情况:x/8g 0x0000000100008158

iOS开发-Swift进阶之值类型 & 引用类型!

问题:此时将t赋值给t1,如果修改了t1,t会发生改变吗?

  • 直接打印t及t1,可以发现t并没有因为t1的改变而改变,主要是因为因为t1t之间是值传递,即t1和t是不同内存空间,是直接将t中的值拷贝至t1中。t1修改的内存空间,是不会影响t的内存空间的

    iOS开发-Swift进阶之值类型 & 引用类型!

SIL验证

同样的,我们也可以通过分析SIL来验证结构体是值类型

  • SIL文件中,我们查看结构体的初始化方法,可以发现只有init,而没有malloc,在其中看不到任何关于堆区的分配

    iOS开发-Swift进阶之值类型 & 引用类型!

总结

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • 结构体是值类型,且结构体的地址就是第一个成员的内存地址
  • 值类型

    • 在内存中直接存储值

    • 值类型的赋值,是一个值传递的过程,即相当于拷贝了一个副本,存入不同的内存空间,两个空间彼此间并不共享状态

    • 值传递其实就是深拷贝

引用类型

类的常用写法

//****** 写法一 *******
class CJLTeacher {
    var age: Int = 18

    func teach(){
        print("teach")
    }
    init(_ age: Int) {
        self.age = age
    }
}
var t = CJLTeacher.init(20)

//****** 写法二 *******
class CJLTeacher {
    var age: Int?

    func teach(){
        print("teach")
    }
    init(_ age: Int) {
        self.age = age
    }
}
var t = CJLTeacher.init(20)
  • 在类中,如果属性没有赋值,也不是可选项,编译会报错

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 需要自己实现init方法

为什么类是引用类型?

定义一个类,通过一个例子来说明

class CJLTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}
var t1 = CJLTeacher1()

类初始化的对象t1,存储在全局区

  • 打印t1、t:po t1,从图中可以看出,t1内存空间中存放的是地址,t中存储的是

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 获取t1变量的地址,并查看其内存情况
    • 获取t1指针地址:po withUnsafePointer(to: &t1){print($0)}
    • 查看t1全局区地址内存情况:x/8g 0x0000000100008218
    • 查看t1地址中存储的堆区地址内存情况:x/8g 0x00000001040088f0

iOS开发-Swift进阶之值类型 & 引用类型!

引用类型 特点

  • 1、地址中存储的是堆区地址

  • 2、堆区地址中存储的是

问题1:此时将t1赋值给t2,如果修改了t2,会导致t1修改吗?

  • 通过lldb调试得知,修改了t2,会导致t1改变,主要是因为t2t1地址中都存储的是 同一个堆区地址,如果修改,修改是同一个堆区地址,所以修改t2会导致t1一起修改,即浅拷贝

    iOS开发-Swift进阶之值类型 & 引用类型!

问题2:如果结构体中包含类对象,此时如果修改t1中的实例对象属性,t会改变吗?

代码如下所示

class CJLTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}

struct CJLTeacher {
    var age: Int = 18
    var age2: Int = 20
    var teacher: CJLTeacher1 = CJLTeacher1()
}

var  t = CJLTeacher()

var t1 = t
t1.teacher.age = 30

//分别打印t1和t中teacher.age,结果如下
t1.teacher.age = 30 
t.teacher.age = 30

从打印结果中可以看出,如果修改t1中的实例对象属性,会导致t中实例对象属性的改变。虽然在结构体中是值传递,但是对于teacher,由于是引用类型,所以传递的依然是地址

同样可以通过lldb调试验证

  • 打印t的地址:po withUnsafePointer(to: &t){print($0)}
  • 打印t的内存情况: x/8g 0x0000000100008238
  • 打印t中teacher地址的内存情况:x/8g 0x000000010070e4a0

iOS开发-Swift进阶之值类型 & 引用类型!

注意:在编写代码过程中,应该尽量避免值类型包含引用类型

查看当前的SIL文件,尽管CJLTeacher1是放在值类型中的,在传递的过程中,不管是传递还是赋值,teacher都是按照引用计数进行管理的

iOS开发-Swift进阶之值类型 & 引用类型!

可以通过打印teacher的引用计数来验证我们的说法,其中teacher的引用计数为3

iOS开发-Swift进阶之值类型 & 引用类型!

主要是是因为:

  • mainretain一次

  • teacher.getter方法中retain一次

  • teacher.setter方法中retain一次

    iOS开发-Swift进阶之值类型 & 引用类型!

mutating

通过结构体定义一个,主要有push、pop方法,此时我们需要动态修改栈中的数组

  • 如果是以下这种写法,会直接报错,原因是值类型本身是不允许修改属性

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 将push方法改成下面的方式,查看SIL文件中的push函数
struct CJLStack {
    var items: [Int] = []
    func push(_ item: Int){
        print(item)
    }
}

iOS开发-Swift进阶之值类型 & 引用类型!

从图中可以看出,push函数除了item,还有一个默认参数selfselflet类型,表示不允许修改

  • 尝试1:如果将push函数修改成下面这样,可以添加进去吗?
struct CJLStack {
    var items: [Int] = []
    func push(_ item: Int){
        var s = self
        s.items.append(item)
    }
}

打印结果如下

iOS开发-Swift进阶之值类型 & 引用类型!

可以得出上面的代码并不能将item添加进去,因为s是另一个结构体对象,相当于值拷贝,此时调用push是将item添加到s的数组中了

  • 根据前文中的错误提示,给push添加mutating,发现可以添加到数组了
struct CJLStack {
    var items: [Int] = []
    mutating func push(_ item: Int){
        items.append(item)
    }
}

查看其SIL文件,找到push函数,发现与之前有所不同,push添加mutating(只用于值类型)后,本质上是给值类型函数添加了inout关键字,相当于在值传递的过程中,传递的是引用(即地址)

iOS开发-Swift进阶之值类型 & 引用类型!

inout关键字

一般情况下,在函数的声明中,默认的参数都是不可变的,如果想要直接修改,需要给参数加上inout关键字

  • 未加inout关键字,给参数赋值,编译报错

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 添加inout关键字,可以给参数赋值

    iOS开发-Swift进阶之值类型 & 引用类型!

总结

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • 1、结构体中的函数如果想修改其中的属性,需要在函数前加上mutating,而类则不用
  • 2、mutating本质也是加一个 inout修饰的self

  • 3、Inout相当于取地址,可以理解为地址传递,即引用

  • 4、mutating修饰方法,而inout 修饰参数

总结

通过上述LLDB查看结构体 & 类的内存模型,有以下总结:

  • 类型,相当于一个本地excel,当我们通过QQ传给你一个excel时,就相当于一个值类型,你修改了什么我们这边是不知道的

  • 引用类型,相当于一个在线表格,当我们和你共同编辑一个在先表格时,就相当于一个引用类型,两边都会看到修改的内容

  • 结构体函数修改属性, 需要在函数前添加mutating关键字,本质是给函数的默认参数self添加了inout关键字,将selflet常量改成了var变量

方法调度

通过上面的分析,我们有以下疑问:结构体和类的方法存储在哪里?下面来一一进行分析

静态派发

值类型对象的函数的调用方式是静态调用,即直接地址调用,调用函数指针,这个函数指针在编译、链接完成后就已经确定了,存放在代码段,而结构体内部并不存放方法。因此可以直接通过地址直接调用

  • 结构体函数调试如下所示

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 打开打开demo的Mach-O可执行文件,其中的__text段,就是所谓的代码段,需要执行的汇编指令都在这里

    iOS开发-Swift进阶之值类型 & 引用类型!

对于上面的分析,还有个疑问:直接地址调用后面是符号,这个符号哪里来的?

iOS开发-Swift进阶之值类型 & 引用类型!

是从Mach-O文件中的符号表Symbol Tables,但是符号表中并不存储字符串,字符串存储在String Table(字符串表,存放了所有的变量名和函数名,以字符串形式存储),然后根据符号表中的偏移值到字符串中查找对应的字符,然后进行命名重整:工程名+类名+函数名,如下所示

iOS开发-Swift进阶之值类型 & 引用类型!

-Symbol Table:存储符号位于字符串表的位置

  • Dynamic Symbol Table动态库函数位于符号表的偏移信息

还可以通过终端命令nm,获取项目中的符号表

  • 查看符号表:nm mach-o文件路径

  • 通过命令还原符号名称:xcrun swift-demangle 符号

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果将edit scheme -> run中的debug改成release,编译后查看,在可执行文件目录下,多一个后缀为dSYM的文件,此时,再去Mach-O文件中查找teach,发现是找不到,其主要原因是因为静态链接的函数,实际上是不需要符号的,一旦编译完成,其地址确定后,当前的符号表就会删除当前函数对应的符号,在release环境下,符号表中存储的只是不能确定地址的符号

  • 对于不能确定地址的符号,是在运行时确定的,即函数第一次调用时(相当于懒加载),例如print,是通过dyld_stub_bind确定地址的(这个在最新版的12.2中通过断点调试并未找到,后续待继续验证,有不同见解的,欢迎留言指出)

    iOS开发-Swift进阶之值类型 & 引用类型!

函数符号命名规则

  • 对于C函数来说,命名的重整规则就是在函数名之前加_(注意:C中不允许函数重载,因为没有办法区分)
#include <stdio.h>
void test(){    }

iOS开发-Swift进阶之值类型 & 引用类型!

  • 对于OC来说,也不支持函数重载,其符号命名规则是-[类名 函数名]

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 对于Swift来说,是云溪函数重载,主要是因为swift中的重整命名规则比较复杂,可以确保函数符号的唯一性

补充:ASLR

关于ASLR的详细说明这里目前不解释了,下面是针对函数地址的一个验证

  • 通过运行发现,Mach-O中的地址与调试时直接获取的地址是由一定偏差的,其主要原因是实际调用时地址多了一个ASLR(地址空间布局随机化 address space layout randomizes)

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 可以通过image list查看,其中0x0000000100000000是程序运行的首地址,后8位是随机偏移00000000(即ASLR)

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 将Mach-O中的文件地址0x0000000100003D50 + 0x00000000 = 0x100003D50,正好对应上面调用的地址

动态派发

汇编指令补充

  • blr:带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址
  • mov:将某一寄存器的值复制到另一寄存器(只能用于寄存器与起存起或者 寄存器与常量之间 传值,不能用于内存地址)
    • mov x1, x0 将寄存器x0的值复制到寄存器x1中
  • ldr:将内存中的值读取到寄存器中
    • ldr x0, [x1, x2] 将寄存器x1和寄存器x2 相加作为地址,取该内存地址的值翻入寄存器x0中
  • str:将寄存器中的值写入到内存中
    • str x0, [x0, x8] 将寄存器x0的值保存到内存[x0 + x8]处
  • bl:跳转到某地址

探索class的调度方式

首先介绍下V_Table在SIL文件中的格式

//声明sil vtable关键字
decl ::= sil-vtable
//sil vtable中包含 关键字、标识(即类名)、所有的方法
2 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
//方法中包含了声明以及函数名称
3 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na
me

例如,以CJLTacher为例,其SIL中的v-table如下所示

class CJLTeacher{
    func teach(){}
    func teach2(){}
    func teach3(){}
    func teach4(){}
    @objc deinit{}
    init(){}
}

iOS开发-Swift进阶之值类型 & 引用类型!

  • sil_vtable:关键字

  • CJLTeacher:表示是CJLTeacher类的函数表

  • 其次就是当前方法的声明对应着方法的名称

  • 函数表 可以理解为 数组,声明在 class内部的方法在不加任何关键字修饰的过程中,是连续存放在我们当前的地址空间中的。这一点,可以通过断点来印证,

    iOS开发-Swift进阶之值类型 & 引用类型!

    • register read x0,此时的地址和 实例对象的地址是相同的,其中x8 实例对象地址,即首地址

      iOS开发-Swift进阶之值类型 & 引用类型!

观察这几个方法的偏移地址,可以发现方法是连续存放的,正好对应V-Table函数表中的排放顺序,即是按照定义顺序排放在函数表中

iOS开发-Swift进阶之值类型 & 引用类型!

函数表源码探索

下面来进行函数表底层的源码探索

  • 源码中搜索initClassVTable,并加上断点,然后写上源码进行调试

    iOS开发-Swift进阶之值类型 & 引用类型!

    其内部是通过for循环编码,然后offset+index偏移,然后获取method,将其存入到偏移后的内存中,从这里可以印证函数是连续存放的

对于class中函数来说,类的方法调度是通过V-Taable,其本质就是一个连续的内存空间(数组结构)。

问题:如果更改方法声明的位置呢?例如extension中的函数,此时的函数调度方式还是函数表调度吗?

通过以下代码验证

  • 定义一个CJLTeacher的extension
extension CJLTeacher{
    func teach5(){ print("teach5") }
}
  • 在定义一个子类CJLStudent继承自CJLTeacher,查看SIL中的V-Table
class CJLStudent: CJLTeacher{}
  • 查看SIL文件,发现子类只继承了class中定义的函数,即函数表中的函数

    iOS开发-Swift进阶之值类型 & 引用类型!

    其原因是因为子类将父类的函数表全部继承了,如果此时子类增加函数,会继续在连续的地址中插入,假设extension函数也是在函数表中,则意味着子类也有,但是子类无法并没有相关的指针记录函数 是父类方法 还是 子类方法,所以不知道方法该从哪里插入,导致extension中的函数无法安全的放入子类中。所以在这里可以侧面证明extension中的方法是直接调用的,且只属于类,子类是无法继承的

开发注意点:

  • 继承方法和属性,不能写extension中。
  • 而extension中创建的函数,一定是只属于自己类,但是其子类也有其访问权限,只是不能继承和重写,如下所示
extension CJLTeacher{
    var age: Int{
        get{
            return 18
        }
    }
    func teach(){
        print("teach")
    }
}

class CJLMiddleTeacher: CJLTeacher{
    override func study() {
        print("CJLMiddleTeacher study")
    }
}

var t = CJLMiddleTeacher()
//子类有父类extension中方法的访问权限,只是不能继承和重写
t.teach()
t.study()
print(t.age)

<!--运行结果-->
teach
CJLMiddleTeacher study
18

final、@objc、dynamic修饰函数

final 修饰

  • final 修饰的方法是 直接调度的,可以通过SIL验证 + 断点验证
class CJLTeacher {
    final func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    init(){}
}

iOS开发-Swift进阶之值类型 & 引用类型!

@objc 修饰

使用@objc关键字是将swift中的方法暴露给OC

class CJLTeacher{
    @objc func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    init(){}
}

通过SIL+断点调试,发现@objc修饰的方法是 函数表调度

iOS开发-Swift进阶之值类型 & 引用类型!

【小技巧】:混编头文件查看方式:查看项目名-Swift.h头文件

!](//upload-images.jianshu.io/upload_images/2251862-dc95c85d154fc67c.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1172/format/webp)

  • 如果只是通过@objc修饰函数,OC还是无法调用swift方法的,因此如果想要OC访问swift,class需要继承NSObject
<!--swift类-->
class CJLTeacher: NSObject {
    @objc func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    override init(){}
}

<!--桥接文件中的声明-->
SWIFT_CLASS("_TtC9_3_指针10CJLTeacher")
@interface CJLTeacher : NSObject
- (void)teach;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

<!--OC调用-->
//1、导入swift头文件
#import "CJLOCTest-Swift.h"
//2、调用
CJLTeacher *t = [[CJLTeacher alloc] init];
[t teach];

查看SIL文件发现被@objc修饰的函数声明有两个:swift + OC(内部调用的swift中的teach函数)

iOS开发-Swift进阶之值类型 & 引用类型!

即在SIL文件中生成了两个方法

  • swift原有的函数
  • @objc标记暴露给OC来使用的函数: 内部调用swift的

dynamic 修饰

以下面代码为例,查看dynamic修饰的函数的调度方式

class CJLTeacher: NSObject {
    dynamic func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    override init(){}
}

其中teach函数的调度还是 函数表调度,可以通过断点调试验证,使用dynamic的意思是可以动态修改,意味着当类继承自NSObject时,可以使用method-swizzling

@objc + dynamic

class CJLTeacher{
    @objc dynamic func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    init(){}
}

通过断点调试,走的是objc_msgSend流程,即 动态消息转发

iOS开发-Swift进阶之值类型 & 引用类型!

场景:swift中实现方法交换

在swift中的需要交换的函数前,使用dynamic修饰,然后通过:@_dynamicReplacement(for: 函数符号)进行交换,如下所示

class CJLTeacher: NSObject {
    dynamic func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    override init(){}
}

extension CJLTeacher{
    @_dynamicReplacement(for: teach)
    func teach5(){
        print("teach5")
    }
}

将teach方法替换成了teach5

iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果teach没有实现 / 如果去掉dynamic修饰符,会报错

    iOS开发-Swift进阶之值类型 & 引用类型!

总结

  • struct类型,其中函数的调度属于直接调用地址,即静态调度

  • class引用类型,其中函数的调度是通过V-Table函数表来进行调度的,即动态调度

  • extension中的函数调度方式是直接调度

  • final修饰的函数调度方式是直接调度

  • @objc修饰的函数调度方式是函数表调度,如果OC中需要使用,class还必须继承NSObject

  • dynamic修饰的函数的调度方式是函数表调度,使函数具有动态性

  • @objc + dynamic 组合修饰的函数调度,是执行的是objc_msgSend流程,即 动态消息转发

补充:内存插件

主要补充内存插件libfooplugin.dylib安装及使用

安装 & 使用

  • 在跟目下创建.lldbinit文件 vim /.lldbinit

  • 然后输入 plugin load libfooplugin.dylib路径

  • 使用:在lldb 调试中输入 -- cat address 地址

可以在这里下载插件文件,密码: go4q

内存分区实践

堆区

有以下代码,通过cat查看t属于哪个区

class CJLTeacher{
    func teach(){

    }
}
let t = CJLTeacher()

iOS开发-Swift进阶之值类型 & 引用类型!

从结果中可以看出,是在堆区,即heap pointer

栈区

查看以下代码的内存地址位于哪个区?

func test(){
    var age: Int = 10
    print(age)
}

iOS开发-Swift进阶之值类型 & 引用类型!

从结果来看,位于栈区,即stack pointer

全局区

对于C的分析

下面是C语言的部分代码,查看其变量的内存地址

//全局已初始化变量
int a = 10;
//全局未初始化变量
int age;

//全局静态变量
static int age2 = 30;

int main(int argc, const char * argv[]) {

    char *p = "CJLTeacher";
    printf("%d", a);
    printf("%d", age2);
    return  0;
}
  • 查看a(全局已初始化变量)的内存地址

    iOS开发-Swift进阶之值类型 & 引用类型!

    其中__DATA.__data表示segment.section,这里的位置和全局区并不冲突,因为一个是人为的内存分配(内存布局分区),一个是Mach-O的segment.section段中,是文件的格式划分

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 查看age(全局未初始化变量)的内存地址

    iOS开发-Swift进阶之值类型 & 引用类型!

    age在Mach-O文件中,放在了__DATA.__common段,主要放的就是未初始化的符号声明(mach-o相比内存划分更细,主要是为了更好的定位符号),当然此时的age 在内存中依然在全局区

  • 查看age2(全局已初始化静态变量)的内存地址(其中需要注意:age2必须使用才能找到,否则会报错)

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 观察3个变量的地址,其地址都是相邻的,因为在内存中都放在了全局区,观察其内存地址,可以发现,在全局区中,未初始化变量地址 比 已初始化变量地址 高

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果定义了一个char *p = "CJLTeacher",查看*p,存储在__TEXT.cstring段,内存中存储在常量区

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果是const修饰的变量呢?存放在Mach-O文件中的__TEXT.__const

    iOS开发-Swift进阶之值类型 & 引用类型!

  • 如果使用static + const修饰变量,此时变量在哪?**
static const int age3 = 40;
  • 查看age3的内存地址,地址特别大,而且使用cat查看不了,因为mach-o没有记录,age3 就是30,即使用static+const修饰的变量就相当于直接替换

    iOS开发-Swift进阶之值类型 & 引用类型!

对于swift的分析

let age = 10

由于是不可变所以不能通过po+cat查看内存,通过汇编 首地址+偏移 来获取age的内存,发现是在Mach-O的__DATA.__common

iOS开发-Swift进阶之值类型 & 引用类型!

从这里可以发现,这与C中是有所区别的。swift的不同之处:已经初始化的全局变量放在__DATA.__common段,猜测是因为 age开始是被标记为未初始化的,当我们执行代码之后才将10存储到对应的内存地址中

  • 如果是var修饰的变量呢?可以发现与let是一致的,还是__DATA.__common
var age2 = 10

iOS开发-Swift进阶之值类型 & 引用类型!

总结

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

  • 对于C语言中全局变量,根据是否已经初始化,存储在Mach-O中存储位置是不同的
  • 初始化的全局变量:__DATA.__data
*   `未`初始化的全局变量:`__DATA.__common`

*   `已`初始化的全局`静态`变量,即`static`修饰:`__DATA.__data`

*   对于`char *p`类型的字符:`__TEXT.cstring`

*   `const`修饰的全局变量:`__TEXT.__const`

*   `static+const`修饰的全局变量:Mach-O中没有记录

原文地址:https://blog.51cto.com/u_15146321/2821449

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。