一文搞懂 ARM 64 系列: 一文搞懂 ARM 64 系列: 函数调用传参与返回值

函数调用涉及到传参与返回值,下面就来看下 ARM 64 中,参数与返回值的传递机制。

1 整数型参数传递

这里的 整数型 并不单指 int 类型,或者 NSInteger 类型,而是指 任何 能够使用整数表示的数据类型,包括 char BOOL 、指针等。

对于整数型参数,需要分成参数个数 <=8 个和 >8 个两种情形来看。

如果参数个数 <= 8 个,那么参数全部使用 Xn 寄存器传递

比如,一个函数的参数只有 4 个,那么就是用 X0 X1 X2 X3 寄存器传递。如果这个函数的参数为 8 个,那么就使用 X0 X1 X2 X3 X4 X5 X6 X7 寄存器传递。

换句话说,寄存器 X0~X7 就是用来在参数个数 <=8 个时,传递参数的。

// 1. 接受 4 个整型参数的函数
NSInteger add4(NSInteger zero, NSInteger one, NSInteger two, NSInteger three) {
    return zero + one + two + three;
}

@implementation ViewController

- (void)viewDidLoad {
    // 2. 调用函数 add4
    NSInteger result = add4(0, 1, 2, 3);
    NSLog(@"%ld", result);
}

@end

上面代码注释 1 定义了一个接受 4 个参数的函数 add4 .

代码注释 2 viewDidLoad 函数中调用了函数 add4

下面来看 viewDidLoad 函数的汇编代码:

// ARMAssemble`-[ViewController viewDidLoad]:
    ...
    0x102e142b0 <+28>: mov    x1, #0x1
   ...
    0x102e142bc <+40>: mov    x0, #0x0
    0x102e142c0 <+44>: mov    x2, #0x2
    0x102e142c4 <+48>: mov    x3, #0x3
    0x102e142c8 <+52>: bl     0x102e14000               ; add4 at ViewController.m:10
    ...

上面代码前面 4 行将参数写入了对应的寄存器,最后一行调用了函数 add4

函数 add4 的汇编代码如下:

ARMAssemble`add4:
->  0x102e14000 <+0>:  sub    sp, sp, #0x20
    0x102e14004 <+4>:  str    x0, [sp, #0x18]
    0x102e14008 <+8>:  str    x1, [sp, #0x10]
    0x102e1400c <+12>: str    x2, [sp, #0x8]
    0x102e14010 <+16>: str    x3, [sp]
    ...

上面代码第 1 行分配栈空间,后面 4 行代码就将参数值存储到了对应的栈空间。

如果参数个数 > 8 个,那么寄存器 X0~X7 负责传递前 8 个参数,剩下的参数使用栈来传递

// 1. 定义接受 10 个参数的函数 add10
NSInteger add10(NSInteger zero, NSInteger one, NSInteger two, NSInteger three, NSInteger four, NSInteger five, NSInteger six, NSInteger seven, NSInteger eight, NSInteger nine) {
    return zero + one + two + three + four + five + six + seven + eight + nine;
}

@implementation ViewController

- (void)viewDidLoad {
    // 2. 调用 add10
    NSInteger result = add10(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    NSLog(@"%ld", result);
}

@end

上面代码注释 1 定义了一个接受 10 个参数的函数 add10

代码注释 2 viewDidLoad 函数中调用了函数 add10

viewDidLoad 函数的汇编代码如下:

ARMAssemble`-[ViewController viewDidLoad]:
    0x10455c294 <+0>:   sub    sp, sp, #0x40
    ...
    0x10455c2b0 <+28>:  mov    x1, #0x1
    ...
    // 1. 存储第 9 个参数到栈中
    0x10455c2bc <+40>:  mov    x9, sp
    0x10455c2c0 <+44>:  mov    x8, #0x8
    0x10455c2c4 <+48>:  str    x8, [x9]
    // 2. 存储第 10 个参数到栈中
    0x10455c2c8 <+52>:  mov    x8, #0x9
    0x10455c2cc <+56>:  str    x8, [x9, #0x8]
    0x10455c2d0 <+60>:  mov    x0, #0x0
    0x10455c2d4 <+64>:  mov    x2, #0x2
    0x10455c2d8 <+68>:  mov    x3, #0x3
    0x10455c2dc <+72>:  mov    x4, #0x4
    0x10455c2e0 <+76>:  mov    x5, #0x5
    0x10455c2e4 <+80>:  mov    x6, #0x6
    0x10455c2e8 <+84>:  mov    x7, #0x7
    // 3. 调用函数 add10
    0x10455c2ec <+88>:  bl     0x10455c048               ; add10 at ViewController.m:14
  ...

上面代码注释 1 3 行代码将第 9 个参数存储到栈顶。

代码注释 2 后面 2 行代码将第 10 个参数存储到栈地址 (SP + 0X8) 处。

代码注释 3 调用函数 add10

函数 add10 的汇编代码如下:

ARMAssemble`add10:
->  0x10455c048 <+0>:   sub    sp, sp, #0x50
    // 1. 从主调函数栈中加载第 9 个参数到寄存器 X9
    0x10455c04c <+4>:   ldr    x9, [sp, #0x50]
    // 2. 从主调函数栈中加载第 10 个参数到寄存器 X8
    0x10455c050 <+8>:   ldr    x8, [sp, #0x58]
    // 3. 下面 8 条语句将对应的参数存储到对应的栈空间
    0x10455c054 <+12>:  str    x0, [sp, #0x48]
    0x10455c058 <+16>:  str    x1, [sp, #0x40]
    0x10455c05c <+20>:  str    x2, [sp, #0x38]
    0x10455c060 <+24>:  str    x3, [sp, #0x30]
    0x10455c064 <+28>:  str    x4, [sp, #0x28]
    0x10455c068 <+32>:  str    x5, [sp, #0x20]
    0x10455c06c <+36>:  str    x6, [sp, #0x18]
    0x10455c070 <+40>:  str    x7, [sp, #0x10]
    // 4. 将第 9 个参数存储到栈地址 SP + 0x8 处
    0x10455c074 <+44>:  str    x9, [sp, #0x8]
    // 5. 将第 10 个参数存储到栈顶
    0x10455c078 <+48>:  str    x8, [sp]

上面代码注释 1 从主调函数栈中加载第 9 个参数到寄存器 X9

代码注释 2 从主调函数栈中加载第 10 个参数到寄存器 X8

代码注释 3 后面 8 条语句将对应参数存储到对应栈空间。

代码注释 4 将第 9 个参数存储到栈地址 (SP + 0x8) 处。

代码注释 5 将第 10 个参数存储到栈顶。

2 浮点数参数

浮点数参数的传递和整数型参数类似:

如果参数个数 <= 8 个,那么参数全部使用 Dn 寄存器传递

如果参数个数 > 8 个,那么寄存器 D0~D7 负责传递前 8 个参数,剩下的参数使用栈传递

3 混合参数

混合参数是指参数中既有整数型参数,也有浮点数参数,那么参数传递规则会分别应用整数型规则和浮点数规则。

比如,如果一个函数有 10 个整数型参数, 10 个浮点数参数,那么参数规则应用如下:

首先应用整数型参数规则,由于参数个数超过了 8 个,前 8 个整数型参数由寄存器 X0~X7 传递,剩余参数使用栈传递。

然后应用浮点数参数规则,由于参数个数超过了 8 个,前 8 个浮点数参数由浮点数寄存器 D0~D7 传递,剩余参数使用栈传递。

// 1. 定义了接受 10 个整数型参数和 10 个浮点数参数的函数 hybrid
CGFloat hybrid(NSInteger zero, NSInteger one, CGFloat zerof, CGFloat onef, CGFloat twof, CGFloat threef, NSInteger two, NSInteger three, NSInteger four, NSInteger five, NSInteger six, NSInteger seven, NSInteger eight, NSInteger nine, CGFloat fourf, CGFloat fivef, CGFloat sixf, CGFloat sevenf, CGFloat eightf, CGFloat ninef) {
    return zero + one + two + three + four + five + six + seven + eight + nine + zerof + onef + twof + threef + fourf + fivef + sixf + sevenf + eightf + ninef;
}

@implementation ViewController

- (void)viewDidLoad {
    // 2. 调用 hybrid 函数,注意第 3 4 5 6个参数是浮点数
    CGFloat result = hybrid(0, 1, 0.0f, 1.0f, 2.0f, 3.0f, 2, 3, 4, 5, 6, 7, 8, 9, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f);
    NSLog(@"%f", result);
}

上面代码注释 1 定义了一个接受 10 个整数型参数和 10 个浮点数参数的函数 hybrid 。需要注意的是,第 3 4 5 6 个参数是浮点数而不是整型数。

代码注释 2 viewDidLoad 函数中调用函数 hybrid

viewDidLoad 函数的汇编码如下:

ARMAssemble`-[ViewController viewDidLoad]:
    0x10059c294 <+0>:   sub    sp, sp, #0x50
    ...
    0x10059c2b0 <+28>:  mov    x1, #0x1
    ...
    // 1. 存储整数型参数中第 9 个参数到栈顶
    0x10059c2bc <+40>:  mov    x8, sp
    0x10059c2c0 <+44>:  mov    x9, #0x8
    0x10059c2c4 <+48>:  str    x9, [x8]
    // 2. 存储整数型参数中第 10 个参数到栈地址 SP + 0x8
    0x10059c2c8 <+52>:  mov    x9, #0x9
    0x10059c2cc <+56>:  str    x9, [x8, #0x8]
    // 3. 存储浮点数参数中第 9 个参数到栈地址 SP + 0x10
    0x10059c2d0 <+60>:  fmov   d0, #8.00000000
    0x10059c2d4 <+64>:  str    d0, [x8, #0x10]
    // 4. 存储浮点数参数中第 10 个参数到栈地址 SP + 0x18
    0x10059c2d8 <+68>:  fmov   d0, #9.00000000
    0x10059c2dc <+72>:  str    d0, [x8, #0x18]
    // 5. 剩下 15 条语句存储对应参数到相应的寄存器
    0x10059c2e0 <+76>:  mov    x0, #0x0
    0x10059c2e4 <+80>:  fmov   d0, xzr
    0x10059c2e8 <+84>:  fmov   d1, #1.00000000
    0x10059c2ec <+88>:  fmov   d2, #2.00000000
    0x10059c2f0 <+92>:  fmov   d3, #3.00000000
    0x10059c2f4 <+96>:  mov    x2, #0x2
    0x10059c2f8 <+100>: mov    x3, #0x3
    0x10059c2fc <+104>: mov    x4, #0x4
    0x10059c300 <+108>: mov    x5, #0x5
    0x10059c304 <+112>: mov    x6, #0x6
    0x10059c308 <+116>: mov    x7, #0x7
    0x10059c30c <+120>: fmov   d4, #4.00000000
    0x10059c310 <+124>: fmov   d5, #5.00000000
    0x10059c314 <+128>: fmov   d6, #6.00000000
    0x10059c318 <+132>: fmov   d7, #7.00000000
    // 6. 调用函数 hybrid
    0x10059c31c <+136>: bl     0x10059c178               ; hybrid at ViewController.m:22

上面代码注释 1 存储整数型参数中第 9 个参数到栈顶。

代码注释 2 存储整数型参数中第 10 个参数到栈地址 (SP +0x8) 处。

代码注释 3 存储浮点数参数中第 9 个参数到栈地址 (SP + 0x10) 处。

代码注释 4 存储浮点数参数中第 10 个参数到站地址 (SP + 0x18) 处。

代码注释 5 后面的 15 条语句村出纳对应参数到对应的寄存器,需要注意的是第 2 条语句使用寄存器 X1 传递第 2 个整数型参数。其中的寄存器 xzr 是全 0 寄存器,也就是这个寄存器的值就是 0

代码注释 6 调用函数 hybrid

hybrid 函数汇编码如下:

ARMAssemble`hybrid:
->  0x10059c178 <+0>:   sub    sp, sp, #0xa0
    // 1. 从主调函数栈中获取整数型参数中第 9 个参数到寄存器 X11
    0x10059c17c <+4>:   ldr    x11, [sp, #0xa0]
    // 2. 从主调函数栈中获取整数型参数中第 10 个参数到寄存器 X10
    0x10059c180 <+8>:   ldr    x10, [sp, #0xa8]
    // 3. 从主调函数栈中获取浮点数参数中第 9 个参数到寄存器 X9
    0x10059c184 <+12>:  ldr    x9, [sp, #0xb0]
    // 4. 从主调函数栈中获取浮点数参数中第 10 个参数到寄存器 X8
    0x10059c188 <+16>:  ldr    x8, [sp, #0xb8]
    // 5. 后面 12 条语句将对应参数存储到对应的栈地址
    0x10059c18c <+20>:  str    x0, [sp, #0x98]
    0x10059c190 <+24>:  str    x1, [sp, #0x90]
    0x10059c194 <+28>:  str    d0, [sp, #0x88]
    0x10059c198 <+32>:  str    d1, [sp, #0x80]
    0x10059c19c <+36>:  str    d2, [sp, #0x78]
    0x10059c1a0 <+40>:  str    d3, [sp, #0x70]
    0x10059c1a4 <+44>:  str    x2, [sp, #0x68]
    0x10059c1a8 <+48>:  str    x3, [sp, #0x60]
    0x10059c1ac <+52>:  str    x4, [sp, #0x58]
    0x10059c1b0 <+56>:  str    x5, [sp, #0x50]
    0x10059c1b4 <+60>:  str    x6, [sp, #0x48]
    0x10059c1b8 <+64>:  str    x7, [sp, #0x40]
    // 6. 将整数型参数中第 9 个参数存储到栈地址 SP + 0x38
    0x10059c1bc <+68>:  str    x11, [sp, #0x38]
    // 7. 将整数型参数中第 10 个参数存储到栈地址 SP + 0x30
    0x10059c1c0 <+72>:  str    x10, [sp, #0x30]
    // 8. 后面 4 条语句继续存储浮点数参数到栈地址
    0x10059c1c4 <+76>:  str    d4, [sp, #0x28]
    0x10059c1c8 <+80>:  str    d5, [sp, #0x20]
    0x10059c1cc <+84>:  str    d6, [sp, #0x18]
    0x10059c1d0 <+88>:  str    d7, [sp, #0x10]
    // 9. 将浮点数参数中第 9 个参数存储到栈地址 SP + 0x8
    0x10059c1d4 <+92>:  str    x9, [sp, #0x8]
    // 10. 将浮点数参数中第 10 个参数存储到栈顶
    0x10059c1d8 <+96>:  str    x8, [sp]
    ...

上面代码注释 1 从主调函数栈中获取整数型参数中的第 9 个参数到寄存器 X11

代码注释 2 从主调函数栈中获取整数型参数中的第 10 个参数到寄存器 X10

代码注释 3 从主调函数栈中获取浮点数参数中的第 9 个参数到寄存器 X9

代码注释 4 从主调函数中获取浮点数参数中的第 10 个参数到寄存器 X8

代码注释 5 后面 12 条语句将对应参数存储到对应的栈地址。

代码注释 6 将整数型参数中第 9 个参数存储到栈地址 (SP + 0x38)

代码注释 7 将整数型参数中第 10 个参数存储到栈地址 (SP + 0x30)

代码注释 8 后面 4 条语句继续存储浮点数参数到栈地址。

代码注释 9 将浮点数参数中第 9 个参数存储到栈地址 (SP + 0x8)

代码注释 10 将浮点数参数中第 10 个参数存储到栈顶。

从上图中可以看到,函数 hybrid 中的参数和参数声明的顺序一样,越左边的参数越靠近栈中高地址。

4 结构体参数

结构体作为参数有 2 种情形。

4.1 HAF 结构体

1 种情形是 HFA(Homogeneous Float-point Aggregates) 结构体。这种结构体的成员全部是浮点数类型,且成员不超过 4 个。

iOS 中,典型的就是 CGRect 类型。

如果是 HFA 结构体,那么其成员都是通过寄存器 Dn 传递

@implementation ViewController

- (void)viewDidLoad {
    CGRect rect = CGRectMake(0.0f, 1.0f, 2.0f, 3.0f);
    // 1. CGRect 作为函数 adds 的参数传递
    CGFloat result = adds(rect);
    NSLog(@"%f", result);
}

@end

上面代码注释 1 将结构体 CGRect 作为参数,传递给函数 adds .

函数 viewDidLoad 的汇编码如下:

ARMAssemble`-[ViewController viewDidLoad]:
->  0x102dd82c4 <+0>:   sub    sp, sp, #0x50
    ...
    // 1. 将参数值加载到对应的 d0 d1 d2 d3 寄存器中进行传递
    0x102dd830c <+72>:  ldr    d0, [sp, #0x10]
    0x102dd8310 <+76>:  ldr    d1, [sp, #0x18]
    0x102dd8314 <+80>:  ldr    d2, [sp, #0x20]
    0x102dd8318 <+84>:  ldr    d3, [sp, #0x28]
    0x102dd831c <+88>:  bl     0x102dd8294               ; adds at ViewController.m:35
    ...

上面代码注释 1 就将 D0 D1 D2 D3 加载对应的参数值,进行传递。

4.2 非 HFA 结构体

2 种情形是非 HFA 结构体,也就是结构体成员不全都是浮点数类型,或者即使是浮点数类型,其成员个数也超过了 4 个。

如果非 HFA 结构体大小 <= 16 Bytes ,那么参数使用寄存器 Xn 传递

// 1. 定义非 HFA 结构体,大小正好是 16 Bytes
typedef struct {
    NSInteger one;
    CGFloat onef;
} Param;

@implementation ViewController

- (void)viewDidLoad {
    Param p;
    p.one = 1;
    p.onef = 1.0f;
    // 2. 使用非 HFA 结构体作为参数,调用函数 adds
    CGFloat result = adds(p);
    NSLog(@"%f", result);
}

@end

上面代码注释 1 定义了一个非 HFA 结构体,其大小正好是 16 Bytes

代码注释 2 使用这个结构体作为参数,调用函数 adds

函数 viewDidLoad 的汇编码如下:

ARMAssemble`-[ViewController viewDidLoad]:
->  0x1041602bc <+0>:   sub    sp, sp, #0x40
    ...
    0x1041602d8 <+28>:  mov    x8, #0x1
    ...
    // 1. 寄存器 X8 存储参数 1,存储到栈地址 SP + 0x10
    0x1041602e4 <+40>:  str    x8, [sp, #0x10]
    // 2. 下面 2 条指令将参数 1.0 存储到栈地址 SP + 0x18
    0x1041602e8 <+44>:  fmov   d0, #1.00000000
    0x1041602ec <+48>:  str    d0, [sp, #0x18]
    // 3. 下面 2 条指令将参数 1 1.0 加载到寄存器 X0 X1 进行传递
    0x1041602f0 <+52>:  ldr    x0, [sp, #0x10]
    0x1041602f4 <+56>:  ldr    x1, [sp, #0x18]
    // 4. 调用函数 adds
    0x1041602f8 <+60>:  bl     0x104160294               ; adds at ViewController.m:31

上面代码注释 1 将参数 1 存储到栈地址 (SP + 0x10)

代码注释 2 后面 2 条指令将参数 1.0 存储到栈地址 (SP + 0x18)

代码注释 3 后面 2 条指令将参数 1 1.0 加载到寄存器 X0 X1 进行传递。

代码注释 4 调用函数 adds

如果非 HFA 结构体的大小 > 16 Bytes ,那么主调函数会先将这个参数拷贝到一个内存区,然后将这个内存区的指针,作为参数传递

// 1. 定义非 HFA 结构体,大小为 32 Bytes
typedef struct {
    NSInteger one;
    CGFloat onef;
    NSInteger two;
    CGFloat twof;
} Param;

// 2. 定义函数 adds,接受一个非 HFA 结构体作为参数
CGFloat adds(Param p) {
    return p.one + p.onef + p.two + p.twof;
}
    

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    Param p;
    p.one = 1;
    p.onef = 1.0f;
    p.two = 2;
    p.twof = 2.0f;
    // 3. 调用 adds 函数
    CGFloat result = adds(p);
    NSLog(@"%f", result);
}

@end

上面代码注释 1 定义了一个非 HFA 结构体 Param ,大小为 32 Bytes

代码注释 2 定义了一个函数 adds ,接受非 HFA 结构作为参数。

代码注释 3 调用 adds 函数

函数 viewDidLoad 的汇编码如下:

ARMAssemble`-[ViewController viewDidLoad]:
    0x1025482dc <+0>:   sub    sp, sp, #0x80
    ...
    // 1. 寄存器 X29 执行栈地址 SP + 0x70
    0x1025482e4 <+8>:   add    x29, sp, #0x70
    ...
    0x1025482f8 <+28>:  mov    x8, #0x1
    ...
    // 2. 参数 1 存储到栈地址 X29 - 0x30 处
    0x102548304 <+40>:  stur   x8, [x29, #-0x30]
    // 3. 后面 2 条指令将参数 1.0 存储到栈地址 X29 - 0x28 处
    0x102548308 <+44>:  fmov   d0, #1.00000000
    0x10254830c <+48>:  stur   d0, [x29, #-0x28]
    // 4. 后面 2 条指令将参数 2 存储到栈地址 X29 - 0x20 处
    0x102548310 <+52>:  mov    x8, #0x2
    0x102548314 <+56>:  stur   x8, [x29, #-0x20]
    // 5. 后面 2 条指令将参数 2.0 存储到栈地址 X29 - 0x18 处
    0x102548318 <+60>:  fmov   d0, #2.00000000
    0x10254831c <+64>:  stur   d0, [x29, #-0x18]
    // 6. 将栈地址 X29 - 0x30 处的值加载到寄存器 Q0,也就是将参数 1 1.0 加载到寄存器 Q0
    0x102548320 <+68>:  ldur   q0, [x29, #-0x30]
    // 7. 寄存器 X0 指向栈地址 SP + 0x10,它最终作为参数传递
    0x102548324 <+72>:  add    x0, sp, #0x10
    // 8. 将寄存器 Q0 的值存储到栈地址 SP + 0x10,也就是将参数 1 1.0 存入到此处
    0x102548328 <+76>:  str    q0, [sp, #0x10]
    // 9. 将栈地址 X29 - 0x20 处的值加载到寄存器 Q0,也就是将参数 2 2.0 加载到寄存器 Q0
    0x10254832c <+80>:  ldur   q0, [x29, #-0x20]
    // 10. 将寄存器 Q0 的值存储到栈地址 SP + 0x20,也就是将参数 2 2.0 存储到粗出
    0x102548330 <+84>:  str    q0, [sp, #0x20]
    // 11. 调用函数 adds
    0x102548334 <+88>:  bl     0x102548294               ; adds at ViewController.m:33
    ...

上面代码注释 1 将寄存器 X29 指向栈地址 (SP + 0x70)

代码注释 2 将参数 1 存储到栈地址 (X29 - 0x30)

代码注释 3 后面 2 条指令将参数 1.0 存储到栈地址 (X29 - 0x28)

代码注释 4 后面 2 条指令将参数 2 存储到栈地址 (X29 - 0x20)

代码注释 5 后面 2 条指令将参数 2.0 存储到栈地址 (X29 - 0x18)

代码注释 2 3 4 5 本质上就是在栈空间创建结构体 Param ,然后为其成员变量赋值。

从上图看到, 结构体高地址成员,在栈内存中也处于高地址

代码注释 6 将栈地址 (X29 - 0x30) 的值存储到寄存器 Q0

寄存器 Q0 是一个 128bit 寄存器,可以存储 2 64bit 数据。换句话说,参数 1 1.0 被存储到寄存器 Q0 ,并且参数 1 在低 64bit ,参数 1.0 在高 64bit

有关 ARM 64 寄存器的介绍可以参看《一个搞懂 ARM 64 系列:寄存器》。

代码注释 7 X0 指向栈地址 (SP + 0x10) ,此时寄存器 X0 做为一块内存的指针,将作为参数传递给函数 adds

代码注释 8 将寄存器 Q0 的值存储到栈地址 (SP + 0x10)

代码注释 6 7 8 的最终效果为:

代码注释 9 将栈地址 (X29 - 0x20) 的值存储到寄存器 Q0 ,也就是将参数 2 2.0 存储到寄存器 Q0

代码注释 10 将寄存器 Q0 的值存储到栈地址 (SP + 0x20) 处。

代码注释 6 8 9 10 本质上将结构体 Param 进行了拷贝,拷贝到栈内存中新地址 (SP + 0x10) 处,此地址内存由寄存器 X0 引用。

此处使用寄存器 X0 传参,是因为没有其他整型参数要传递。

代码注释 11 调用函数 adds

5 返回值

返回值的传递规则比较简单。

要确定返回值如何传递,只需要假想, 如果这个返回值作为函数参数,将如何传递 。参数传递的方式,决定了返回值传递的方式。

如果返回值是一个整数型,假想它作为函数参数传递,根据规则,将使用寄存器 X0 传递,那么这个返回值就使用 X0 返回。

如果返回值是一个浮点数,假想它作为函数参数传递,根据规则,将使用寄存器 D0 传递,那么这个返回值就使用 D0 返回。

如果返回值是一个 HFA 结构体,假想它作为函数参数传递,根据规则,将使用寄存器 Dn 传递,那么这个返回值就使用 Dn 返回。

一个例子就是有函数返回 iOS 中的结构体 CGRect ,返回值最终使用寄存器 D0 D1 D2 D3 返回。

如果返回值是一个非 HFA 结构体,并且大小不超过 16 Bytes ,假想它作为参数传递,根据规则,将使用寄存器 Xn 传递,那么返回值就使用 Xn 返回。

如果返回值是一个非 HFA 结构体,并且大小超过了 16 Bytes ,假想它作为函数参数传递,根据规则,主调函数会先拷贝到一块内存区,将这块内存指针传递给被调函数,那么这个返回值也会由被调函数直接通过这个指针,写入主调函数预先开辟的内存中。 只是需要注意的是,指向这块内存区域的 X8 寄存器

// 1. 定义一个非 HFA 结构体,结构体大小为 32 Bytes
typedef struct {
    NSInteger one;
    CGFloat onef;
    NSInteger two;
    CGFloat twof;
} Param;

// 2. 定义函数 adds,它返回非 HFA 结构体 Param
Param adds(void) {
    Param p;
    p.one = 1f;
    p.onef = 1.0f;
    p.two = 2;
    p.twof = 2.0f;
    return p;
}

@implementation ViewController

- (void)viewDidLoad {
    // 3. 调用函数 adds
    Param result = adds();
    NSLog(@"%ld", result.one);
}

@end

代码注释 1 定义一个非 HFA 结构体,结构体大小 32 Bytes

代码注释 2 定义函数 adds ,它返回非 HFA 结构体 Param

代码注释 3 调用函数 adds

函数 viewDidLoad 的汇编码:

ARMAssemble`-[ViewController viewDidLoad]:
    0x104e1c2c8 <+0>:  sub    sp, sp, #0x50
    ...
    // 1. 寄存器 X8 指向栈地址 SP + 0x10,从 SP + 0x10 开始连续 4 个 64bit 区域用来接收返回值
    0x104e1c2ec <+36>: add    x8, sp, #0x10
    0x104e1c2f0 <+40>: bl     0x104e1c294               ; adds at ViewController.m:33
    ...

上面代码注释 1 将寄存器 X8 指向栈地址 (SP + 0x10) ,从 (SP + 0x10) 开始连续 4 64bit 内存区域用来接收返回值。

函数 adds 的汇编码:

ARMAssemble`adds:
    ...
    0x10212c29c <+8>:  mov    x9, #0x1
    ...
    // 1. 寄存器 X9 存储值 1,写入寄存器 X8 指向地址
    0x10212c2a8 <+20>: str    x9, [x8]
    // 2. 后面 2 条指令将 1.0 写入 X8 + 0x8 地址处
    0x10212c2ac <+24>: fmov   d0, #1.00000000
    0x10212c2b0 <+28>: str    d0, [x8, #0x8]
    // 3. 后面 2 条指令将 2 写入 X8 + 0x10 地址处
    0x10212c2b4 <+32>: mov    x9, #0x2
    0x10212c2b8 <+36>: str    x9, [x8, #0x10]
    // 4. 后面 2 条指令将 2.0 写入 X8 + 0x18 地址处
    0x10212c2bc <+40>: fmov   d0, #2.00000000
    0x10212c2c0 <+44>: str    d0, [x8, #0x18]
    0x10212c2c4 <+48>: ret   

上面代码注释 1 将寄存器 X9 存储值 1 ,写入寄存器 X8 指向地址。

代码注释 2 后面 2 条指令将 1.0 写入 (X8 + 0x8) 地址处。

代码注释 3 后面 2 条指令将 2 写入 (X8 + 0x10) 地址处。

代码注释 4 后面 2 条指令将 2.0 写入 (X8 + 0x18) 地址处。

热门手游下载
相关文章