函式呼叫涉及到傳參與返回值,下面就來看下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)
地址處。