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

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

1 整数型参数传递

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

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

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

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

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

objectivec 复制代码
// 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.

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

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

c 复制代码
// 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的汇编代码如下:

c 复制代码
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个参数,剩下的参数使用栈来传递

objectivec 复制代码
// 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

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

viewDidLoad函数的汇编代码如下:

c 复制代码
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
  ...

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

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

代码注释3调用函数add10

函数add10的汇编代码如下:

c 复制代码
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传递,剩余参数使用栈传递。

objectivec 复制代码
// 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个参数是浮点数而不是整型数。

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

viewDidLoad函数的汇编码如下:

c 复制代码
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函数汇编码如下:

c 复制代码
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传递

objectivec 复制代码
@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的汇编码如下:

c 复制代码
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传递

objectivec 复制代码
// 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的汇编码如下:

c 复制代码
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,那么主调函数会先将这个参数拷贝到一个内存区,然后将这个内存区的指针,作为参数传递

objectivec 复制代码
// 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的汇编码如下:

c 复制代码
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寄存器,可以存储264bit数据。换句话说,参数1 1.0被存储到寄存器Q0,并且参数1在低64bit,参数1.0在高64bit

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

代码注释7X0指向栈地址(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寄存器

objectivec 复制代码
// 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的汇编码:

c 复制代码
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)开始连续464bit内存区域用来接收返回值。

函数adds的汇编码:

c 复制代码
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)地址处。

相关推荐
aspirestro三水哥6 天前
4.4.5 timer中断流向Linux(从interrupt log回放)
linux·运维·服务器·arm64·xenomai
chaoguo12348 天前
一文搞懂 ARM 64 系列: ADD(立即数版)
add·arm64
chaoguo123410 天前
一文搞懂 ARM 64 系列: PACISB
arm64·pacibsp
CYRUS STUDIO14 天前
ARM64汇编寻址、汇编指令、指令编码方式
android·汇编·arm开发·arm·arm64
CYRUS STUDIO20 天前
详解ARM64可执行程序的生成过程
android·c语言·汇编·c++·gdb·arm64
灵猫小西1 个月前
鸿蒙HarmonyOS之跳转页面并传递参数的方法
鸿蒙·参数传递·跳转页面
鹏大师运维2 个月前
Mac M芯片上安装统信UOS 1070arm64虚拟机
linux·macos·信创·国产化·arm64·统信·uos1070
橘色的喵5 个月前
针对ARM64嵌入式系统的Linux内核参数优化
性能优化·linux内核·嵌入式·arm·内存管理·arm64·网络优化
韩曙亮5 个月前
【JavaScript】简单数据类型 与 复杂数据类型 ② ( 简单数据类型参数传递 | 复杂数据类型参数传递 )
开发语言·前端·javascript·参数传递·简单数据类型·复杂数据类型·地址传递