一文搞懂 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)
地址处。