一文搞懂 ARM 64 系列: 一文搞懂 ARM 64 系列: 函式呼叫傳參與返回值

chaoguo1234發表於2024-06-09

函式呼叫涉及到傳參與返回值,下面就來看下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個時,傳遞引數的。

// 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函式的彙編程式碼:

// 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行程式碼就將引數值儲存到了對應的棧空間。

image

如果引數個數 >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

程式碼註釋2viewDidLoad函式中呼叫了函式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
  ...

上面程式碼註釋13行程式碼將第9個引數儲存到棧頂。

程式碼註釋2後面2行程式碼將第10個引數儲存到棧地址(SP + 0X8)處。

image

程式碼註釋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個引數儲存到棧頂。

image

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個引數是浮點數而不是整型數。

程式碼註釋2viewDidLoad函式中呼叫函式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

image

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個引數儲存到棧頂。

image

從上圖中可以看到,函式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,然後為其成員變數賦值。

image

從上圖看到,結構體高地址成員,在棧記憶體中也處於高地址

程式碼註釋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的最終效果為:

image

程式碼註釋9將棧地址(X29 - 0x20)的值儲存到暫存器Q0,也就是將引數2 2.0儲存到暫存器Q0

程式碼註釋10將暫存器Q0的值儲存到棧地址(SP + 0x20)處。

程式碼註釋6 8 9 10本質上將結構體Param進行了複製,複製到棧記憶體中新地址(SP + 0x10)處,此地址記憶體由暫存器X0引用。

此處使用暫存器X0傳參,是因為沒有其他整型引數要傳遞。

image

程式碼註釋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)開始連續464bit記憶體區域用來接收返回值。

函式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)地址處。

image

相關文章