函数调用涉及到传参与返回值,下面就来看下
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)
地址处。