美文网首页
汇编(三)

汇编(三)

作者: 浅墨入画 | 来源:发表于2021-04-10 17:17 被阅读0次

    一. 函数的参数

    1.1 多个参数
    创建空工程001--Demo,编写代码如下

    // ViewController.m文件内容
    @implementation ViewController
    
    int test(int a,int b,int c,int d,int e,int f,int g,int h,int i){
        return a + b + c + d + e + f + g + h + i;
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        test(1, 2, 3, 4, 5, 6, 7, 8, 9);
    }
    @end
    

    test方法调用处打断点,运行工程断点执行到汇编代码中,如下图

    image.png

    接下来我们分析viewdidload栈空间存放过程,viewdidload汇编代码如下

    001--Demo`-[ViewController viewDidLoad]:
        0x10012a578 <+0>:   sub    sp, sp, #0x40             ; =0x40 
        0x10012a57c <+4>:   stp    x29, x30, [sp, #0x30]
        0x10012a580 <+8>:   add    x29, sp, #0x30            ; =0x30 
        0x10012a584 <+12>:  stur   x0, [x29, #-0x8]
        0x10012a588 <+16>:  stur   x1, [x29, #-0x10]
        0x10012a58c <+20>:  ldur   x8, [x29, #-0x8]
        0x10012a590 <+24>:  add    x9, sp, #0x10             ; =0x10 
        0x10012a594 <+28>:  str    x8, [sp, #0x10]
        0x10012a598 <+32>:  adrp   x8, 2
        0x10012a59c <+36>:  add    x8, x8, #0xd18            ; =0xd18 
        0x10012a5a0 <+40>:  ldr    x8, [x8]
        0x10012a5a4 <+44>:  str    x8, [x9, #0x8]
        0x10012a5a8 <+48>:  adrp   x8, 2
        0x10012a5ac <+52>:  add    x8, x8, #0xd00            ; =0xd00 
        0x10012a5b0 <+56>:  ldr    x1, [x8]
        0x10012a5b4 <+60>:  mov    x0, x9
        0x10012a5b8 <+64>:  bl     0x10012aa00               ; symbol stub for: objc_msgSendSuper2
    // adrp 表示 address page,内存是分页的
    // adrp   x8, 2   add    x8, x8, #0xd18            ; =0xd18   表示从内存中取数据
    // symbol stub for: objc_msgSendSuper2 表示调用[super viewDidLoad];
    

    首先执行sub sp, sp, #0x40 拉伸栈空间

    sp寄存器拉伸前指向地址 sp寄存器拉伸后指向地址

    接下来执行汇编stp x29, x30, [sp, #0x30],把x29 x30寄存器放入栈中

    image.png

    执行add x29, sp, #0x30,让FP寄存器指向栈底

    image.png

    从SP到FP就是拉伸的栈空间
    字符串常量
    NSLog(@"%@",self); 其中 @"%@" 就是字符串常量
    注意⚠️ 开辟空间是以16字节对齐,操作数据可以是8字节也可以是16字节
    继续执行上面代码,跳入test汇编代码中

    001--Demo`test:
        0x104e364d0 <+0>:   sub    sp, sp, #0x30             ; =0x30 
        0x104e364d4 <+4>:   ldr    w8, [sp, #0x30]   // 从上一个函数栈中取数据,接近函数栈顶的区域,有可能作为函数参数的传递
        0x104e364d8 <+8>:   str    w0, [sp, #0x2c]
        0x104e364dc <+12>:  str    w1, [sp, #0x28]
        0x104e364e0 <+16>:  str    w2, [sp, #0x24]
        0x104e364e4 <+20>:  str    w3, [sp, #0x20]
        0x104e364e8 <+24>:  str    w4, [sp, #0x1c]
        0x104e364ec <+28>:  str    w5, [sp, #0x18]
        0x104e364f0 <+32>:  str    w6, [sp, #0x14]
        0x104e364f4 <+36>:  str    w7, [sp, #0x10]
        0x104e364f8 <+40>:  str    w8, [sp, #0xc]
    ->  0x104e364fc <+44>:  ldr    w8, [sp, #0x2c]
        0x104e36500 <+48>:  ldr    w9, [sp, #0x28]
        0x104e36504 <+52>:  add    w8, w8, w9
        0x104e36508 <+56>:  ldr    w9, [sp, #0x24]
        0x104e3650c <+60>:  add    w8, w8, w9
        0x104e36510 <+64>:  ldr    w9, [sp, #0x20]
        0x104e36514 <+68>:  add    w8, w8, w9
        0x104e36518 <+72>:  ldr    w9, [sp, #0x1c]
        0x104e3651c <+76>:  add    w8, w8, w9
        0x104e36520 <+80>:  ldr    w9, [sp, #0x18]
        0x104e36524 <+84>:  add    w8, w8, w9
        0x104e36528 <+88>:  ldr    w9, [sp, #0x14]
        0x104e3652c <+92>:  add    w8, w8, w9
        0x104e36530 <+96>:  ldr    w9, [sp, #0x10]
        0x104e36534 <+100>: add    w8, w8, w9
        0x104e36538 <+104>: ldr    w9, [sp, #0xc]
        0x104e3653c <+108>: add    w0, w8, w9   // 最终相加的结果给了wo寄存器
        0x104e36540 <+112>: add    sp, sp, #0x30             ; =0x30 
        0x104e36544 <+116>: ret    
    // 参数超过8个,效率就会降低,因为存在函数栈传递参数
    

    现在把工程001--Demo切换到release模式下,运行工程发现并没有调用test汇编,原因是test方法的运行对整个程序并没有作用,编译器会把无用的代码屏蔽掉

    image.png

    现在修改viewcontroller.m文件内容如下

    // ViewController.m文件内容
    @implementation ViewController
    
    int test(int a,int b,int c,int d,int e,int f,int g,int h,int i){
        return a + b + c + d + e + f + g + h + i;
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        printf(@"%d",test(1, 2, 3, 4, 5, 6, 7, 8, 9));
    }
    @end
    

    运行工程发现并没有调用test汇编,编译器直接算出来结果,release模式下编译器会进行优化

    image.png

    1.2 汇编实现参数调用

    // ViewController.m文件内容
    @implementation ViewController
    
    int funcA(int a, int b);
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        int a = funcA(10, 20);
        printf(@"%d", a);
    }
    @end
    
    // asm.s文件内容
    .text
    .global _funcA,_sum
    
    _funcA:
        sub sp,sp,#0x10
        stp x29,x30,[sp]
        bl _sum
        stp x29,x30,[sp]
        add sp,sp,#0x10
        ret
    
    // _funcA也可以这样简写
    _funcA:
        stp x29,x30,[sp,#-0x10]!
        bl _sum
        stp x29,x30,[sp],#0x10
        ret
    
    _sum:
        add x0,x0,x1
        ret
    // 成功运行打印30
    

    二. 函数的返回值

    // ViewController.m文件内容
    @implementation ViewController
    
    struct str {
        int a;
        int b;
        int c;
        int d;
        int f;
        int g;
    };
    
    struct str getStr(int a,int b,int c,int d,int f,int g){
        struct str str1;
        str1.a = a;
        str1.b = b;
        str1.c = d;
        str1.d = d;
        str1.f = f;
        str1.g = g;
        return str1;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
    }
    @end
    

    下图add x8, sp, #0x8 让x8寄存器指向开辟的结构体空间,这里为什么不用sp寄存器呢?

    • 因为调用getStr之后会拉伸栈空间,sp就会发生变化,如果以sp寄存器作为参照,就会找不到创建的结构体。
    image.png
    // getStr 方法汇编代码如下
    001--Demo`getStr:
    ->  0x104f19d94 <+0>:  sub    sp, sp, #0x20             ; =0x20 
        //  这里把1 ~ 6 分别存入栈中
        0x104f19d98 <+4>:  str    w0, [sp, #0x1c]
        0x104f19d9c <+8>:  str    w1, [sp, #0x18]
        0x104f19da0 <+12>: str    w2, [sp, #0x14]
        0x104f19da4 <+16>: str    w3, [sp, #0x10]
        0x104f19da8 <+20>: str    w4, [sp, #0xc]
        0x104f19dac <+24>: str    w5, [sp, #0x8]
        // 上面是把6个参数放入栈中,是从w0 ~ w6,下面为什么直接跳转到w9寄存器,不用w6寄存器呢?
        因为w0 ~ w7 都是用作参数,参数不够的话,w6 w7 两个寄存器不会用到。w8用作返回值,所以此处用w9寄存器作为临时变量
        // 再从栈中取出放入w9寄存器
        0x104f19db0 <+28>: ldr    w9, [sp, #0x1c]
        // 这里以x8寄存器作为参照,把数据写入上一个栈空间中(viewDidLoad栈空间)
        0x104f19db4 <+32>: str    w9, [x8]
        // 以下都是以w9寄存器作为参照取出数据,再把数据写入上一个栈空间
        0x104f19db8 <+36>: ldr    w9, [sp, #0x18]
        0x104f19dbc <+40>: str    w9, [x8, #0x4]
        0x104f19dc0 <+44>: ldr    w9, [sp, #0x14]
        0x104f19dc4 <+48>: str    w9, [x8, #0x8]
        0x104f19dc8 <+52>: ldr    w9, [sp, #0x10]
        0x104f19dcc <+56>: str    w9, [x8, #0xc]
        0x104f19dd0 <+60>: ldr    w9, [sp, #0xc]
        0x104f19dd4 <+64>: str    w9, [x8, #0x10]
        0x104f19dd8 <+68>: ldr    w9, [sp, #0x8]
        0x104f19ddc <+72>: str    w9, [x8, #0x14]
        // 执行到这里之后,就会把结构体数据全部写入上一个栈空间中
        0x104f19de0 <+76>: add    sp, sp, #0x20             ; =0x20 
        0x104f19de4 <+80>: ret 
    
    // 这里并没有用x0寄存器作为返回值,如果我们的函数参数大于8个,返回值也会用栈空间来做
    

    小结
    函数返回值如果一个寄存器能够放的下,就使用x0或wo寄存器,如果放不下的话就会用到栈空间

    三. 函数的局部变量

    // ViewController.m文件内容 
    @implementation ViewController
    int funcB(int a,int b){
        int c = 6;
        return a + b + c;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
         funcB(10, 20);
    }
    @end 
    
    image.png
    // 查看funB函数的汇编代码
    001--Demo`funcB:
    ->  0x1040d5ed4 <+0>:  sub    sp, sp, #0x10             ; =0x10 
        0x1040d5ed8 <+4>:  str    w0, [sp, #0xc]
        0x1040d5edc <+8>:  str    w1, [sp, #0x8]
        // 局部变量c  值为6
        0x1040d5ee0 <+12>: mov    w8, #0x6
        // 局部变量6存入栈中,这里是4个字节
        0x1040d5ee4 <+16>: str    w8, [sp, #0x4]
        0x1040d5ee8 <+20>: ldr    w8, [sp, #0xc]
        0x1040d5eec <+24>: ldr    w9, [sp, #0x8]
        0x1040d5ef0 <+28>: add    w8, w8, w9
        // 取出局部变量6,存入w9临时寄存器
        0x1040d5ef4 <+32>: ldr    w9, [sp, #0x4]
        0x1040d5ef8 <+36>: add    w0, w8, w9
        0x1040d5efc <+40>: add    sp, sp, #0x10             ; =0x10 
        0x1040d5f00 <+44>: ret 
    

    局部变量存入栈空间中,参数用的寄存器传递,所有计算都是通过寄存器来完成。一旦执行 add sp, sp, #0x10 栈平衡,所有局部变量就会取不到相当于销毁了,栈平衡销毁局部变量。
    如果函数嵌套,参数和局部变量会怎么样呢?下面讨论

    四. 函数的嵌套调用

    // ViewController.m文件内容 
    @implementation ViewController
    int funcB(int a,int b){
        int c = 6;
        int d = funcSum(a, b, c);
        int e = funcSum(a, b, c);
        return e;
    }
    
    int funcSum(int a,int b,int c){
        int d = a + b + c;
        printf("%d",d);
        return d;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        funcB(10, 20);
    }
    @end
    
    // 查看funB函数的汇编代码
    001--Demo`funcB:
        // 前面三行汇编类似于函数开始
    ->  0x1004f9e38 <+0>:  sub    sp, sp, #0x30             ; =0x30 
        // x29/fp寄存器 x30/lr寄存器 入栈
        0x1004f9e3c <+4>:  stp    x29, x30, [sp, #0x20]
        // x29指向栈底
        0x1004f9e40 <+8>:  add    x29, sp, #0x20            ; =0x20 
        // 参数入栈保护w0 w1 寄存器,这里只用到两个参数(funcB的两个参数),如果参数多的话,防止函数调用完改变这两个参数的值,而下面又需要使用这几个参数,就需要把多个参数都保护起来
        0x1004f9e44 <+12>: stur   w0, [x29, #-0x4]
        0x1004f9e48 <+16>: stur   w1, [x29, #-0x8]
        // 局部变量生成
        0x1004f9e4c <+20>: mov    w8, #0x6
        // 局部变量入栈
        0x1004f9e50 <+24>: stur   w8, [x29, #-0xc]
        // 这里与下面都用到了 w0 w1 w2 三个寄存器,funcSum也用到这三个寄存器,有可能会改变这三个寄存器的值
        0x1004f9e54 <+28>: ldur   w0, [x29, #-0x4]
        0x1004f9e58 <+32>: ldur   w1, [x29, #-0x8]
        0x1004f9e5c <+36>: ldur   w2, [x29, #-0xc]
        // 调用新的函数funcSum
        0x1004f9e60 <+40>: bl     0x1004f9e8c               ; funcSum at ViewController.m:24
        // 保存函数funcSum的返回值
        0x1004f9e64 <+44>: str    w0, [sp, #0x10]
        // 这里与上面都用到了 w0 w1 w2 三个寄存器,funcSum也用到这三个寄存器,有可能会改变这三个寄存器的值
        0x1004f9e68 <+48>: ldur   w0, [x29, #-0x4]
        0x1004f9e6c <+52>: ldur   w1, [x29, #-0x8]
        0x1004f9e70 <+56>: ldur   w2, [x29, #-0xc]
        0x1004f9e74 <+60>: bl     0x1004f9e8c               ; funcSum at ViewController.m:24
        // 第二次调用函数funcSum,保存返回值
        0x1004f9e78 <+64>: str    w0, [sp, #0xc]
        0x1004f9e7c <+68>: ldr    w0, [sp, #0xc]
        // 下面三行相当于函数结束
        0x1004f9e80 <+72>: ldp    x29, x30, [sp, #0x20]
        0x1004f9e84 <+76>: add    sp, sp, #0x30             ; =0x30 
        0x1004f9e88 <+80>: ret 
    

    小结
    寄存器的保护也叫现场保护,会保护到自己的栈空间中。寄存器保护是保护将来可能会用到这些寄存器。x29 x30 的保护就是这样的

    五. 状态寄存器

    • CPU内部的寄存器中,有一种特殊的寄存器,这种寄存器在ARM中被称为状态寄存器,也就是CPSR(current program status register)寄存器
    • 状态寄存器对于不同的处理器,个数和结构都可能不同
    • CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.

    注:CPSR寄存器是32位的

    • CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位!
    • N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
    • 中间27~8 位为保留位,保留的目的是为了升级 更新 扩展等
    image.png

    5.1 案例一

    // ViewController.m文件内容 
    @implementation ViewController
    void func(){
        int a = 1;
        int b = 2;
        if (a == b) {
            printf("a == b\n");
        }else{
            printf("error\n");
        }
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        func();
    }
    @end
    

    标志寄存器可以用来调试信息,因为它与程序执行流程有关

    // 查看fun函数的汇编代码
    001--Demo`func:
    ->  0x10274de80 <+0>:  sub    sp, sp, #0x20             ; =0x20 
        0x10274de84 <+4>:  stp    x29, x30, [sp, #0x10]
        0x10274de88 <+8>:  add    x29, sp, #0x10            ; =0x10 
        0x10274de8c <+12>: mov    w8, #0x1
        0x10274de90 <+16>: stur   w8, [x29, #-0x4]
        0x10274de94 <+20>: mov    w8, #0x2
        0x10274de98 <+24>: str    w8, [sp, #0x8]
        0x10274de9c <+28>: ldur   w8, [x29, #-0x4]
        0x10274dea0 <+32>: ldr    w9, [sp, #0x8]
        // cmp指令 compare比较
        0x10274dea4 <+36>: cmp    w8, w9
        // b指令跳转 .ne 指的是有条件的跳转,这里的跳转就与标志寄存器有关
        0x10274dea8 <+40>: b.ne   0x10274debc               ; <+60> at ViewController.m
        0x10274deac <+44>: adrp   x0, 1
        0x10274deb0 <+48>: add    x0, x0, #0x62f            ; =0x62f 
        0x10274deb4 <+52>: bl     0x10274e56c               ; symbol stub for: printf
        0x10274deb8 <+56>: b      0x10274dec8               ; <+72> at ViewController.m:77:1
        0x10274debc <+60>: adrp   x0, 1
        0x10274dec0 <+64>: add    x0, x0, #0x637            ; =0x637 
        0x10274dec4 <+68>: bl     0x10274e56c               ; symbol stub for: printf
        0x10274dec8 <+72>: ldp    x29, x30, [sp, #0x10]
        0x10274decc <+76>: add    sp, sp, #0x20             ; =0x20 
        0x10274ded0 <+80>: ret 
    
    image.png image.png

    上面的if判断中 a=1 b=2,为什么修改了cpsr寄存器的值会执行 a==b 的逻辑? 我们下节课讨论!!!

    5.2 案例二

    // ViewController.m文件内容 
    @implementation ViewController
    void func(){
        asm(
            "mov w0,#0xffffffff\n"
            //adds 可以改变标志位
            "adds w0,w0,#0x0\n"
            );
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        func();
    }
    @end
    
    // 查看fun函数的汇编代码
    001--Demo`func:
        // 0xffffffff 就是 -1
    ->  0x100b6decc <+0>: mov    w0, #-0x1
        0x100b6ded0 <+4>: adds   w0, w0, #0x0              ; =0x0 
        0x100b6ded4 <+8>: ret  
    
    image.png image.png

    N(Negative)标志

    CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.

    • 注意,在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如add\sub\or等,他们大都是运算指令(进行逻辑或算数运算);

    上面执行到adds w0, w0, #0x0 标志寄存器是 0110,那么根据N标志的分析,程序执行完成 0110 第一位的0 应该变成 1

    截屏2021-04-10 下午3.24.27.png

    5.3 案例三

    Z(Zero)标志

    CPSR的第30位是Z,0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.

    • Z的值,我们可以这样来看,Z标记相关指令的计算结果是否为0,如果为0,则Z要记录下是0这样的肯定信息.在计算机中1表示逻辑真,表示肯定.所以当结果为0的时候Z = 1,表示结果是0.如果结果不为0,则Z要记录下不是0这样的否定信息.在计算机中0表示逻辑假,表示否定,所以当结果不为0的时候Z = 0,表示结果不为0。

    根据N Z 标志的分析,如果执行结果为0, 那么前两位N Z标志一定是01,下面进行验证

    // ViewController.m文件内容 
    @implementation ViewController
    void func(){
        asm(
            "mov w0,#0x0\n"
            //adds 可以改变标志位
            "adds w0,w0,#0x0\n"
            );
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        func();
    }
    @end
    
    image.png

    5.4 案例四

    // ViewController.m文件内容 
    @implementation ViewController
    void func(){
        asm(
            "mov w0,#0x0\n"
            //adds 可以改变标志位
            "adds w0,w0,#0x1\n"
            );
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        func();
    }
    @end
    

    w0 的值为1 是正书,根据上面分析 N Z标志位都是0,运行得到状态寄存器的值为0x00000000 得到验证

    C(Carry)标志

    • CPSR的第29位是C,进位标志位。一般情况下,进行无符号数的运算。
    • 加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
    • 减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。
    • 作为有符号位 0111 是正数 1111 是负数,作为无符号位 0111是7,1111 是15 举例无符号位 1111 再加上 1 就会溢出变成 100000000,1溢出到C标志位,同理如果减法运算,不够减就从C标志借来数据

    对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:

    image.png

    进位

    • 我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令
    mov w0,#0xaaaaaaaa;0xa 的二进制是 1010
    adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
    adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
    adds w0,w0,w0; 重复上面操作
    adds w0,w0,w0
    

    借位

    • 当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:
    mov w0,#0x0
    subs w0,w0,#0xff ;
    subs w0,w0,#0xff
    subs w0,w0,#0xff
    

    5.5 案例五

    // ViewController.m文件内容 
    @implementation ViewController
    void func(){
        asm(
            // w0 是4个字节,0xaaaaaaaa 相当于 1010 1010 1010 1010 1010 1010 1010 1010
            "mov w0,#0xaaaaaaaa\n"
            // 乘以2 相当于左移1位
            "adds w0,w0,w0\n"
            "adds w0,w0,w0\n"
            "adds w0,w0,w0\n"
            "adds w0,w0,w0\n"
            );
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        func();
    }
    @end
    
    image.png

    上图C为1 相当于溢出,这时修改寄存器cpsr为0x00000000,一步步执行,发现C位不断0 1 替换

    V(Overflow)溢出标志

    • CPSR的第28位是V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
    • 正数 + 正数 为负数 溢出
    • 负数 + 负数 为正数 溢出
    • 正数 + 负数 不可能溢出

    注意⚠️这里的计算数都是相同宽度的
    上面执行的过程,状态寄存器并不知道两个数是正数还是负数,也不知道是有符号数还是无符号数?
    cpsr寄存器的 v位会认为是有符号数运算,c位为无符号数运算,高级语言中有符号数与无符号数的运算,有没有溢出状态寄存器的 V C 位都会给出反馈

    六. 面试题

    6.1 面试题一
    函数A调用函数B,A调用B的时候给B传递了参数,当函数B执行完成后,B函数的参数释放了吗?
    这里以上面 函数的返回值 代码举例

    - (void)viewDidLoad {
        [super viewDidLoad];
        struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
    }
    

    getStr(1, 2, 3, 4, 5, 6)方法中的 1 2 3 4 5 6 对于viewDidLoad方法来说,相当于局部变量,这时候如果参数不是6个而是9个,getStr(1, 2, 3, 4, 5, 6, 7, 8, 9); 其中7 8 9 三个参数是存放在viewDidLoad函数栈中,所以当getStr方法执行完成,这仨参数并没有释放,只有viewDidLoad执行完成才会释放。
    6.2 面试题二
    getStr函数调用之前开辟空间没?如果开辟了,开辟的是栈空间还是堆空间?

    - (void)viewDidLoad {
        [super viewDidLoad];
        struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
    }
    

    这里开辟的是栈空间,开辟了栈空间之后,整个结构体都在栈中,占用了24字节内存,getStr执行完成之后,str2结构体依然在栈中。

    如果viewDidLoad又返回值,并且返回的是 return &str2; 外界能否访问str2的地址来操纵结构体?
    不能,因为str2整个结构体在栈空间中,viewDidLoad执行完成,str2结构体就会释放掉。一般来说不会直接返回结构体,而是返回结构体的指针,要想返回结构体指针,结构体需要开辟在堆空间,开辟完成之后再给结构体赋值,最后再返回结构体指针。

    相关文章

      网友评论

          本文标题:汇编(三)

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