C程式設計語言的彙編解釋 第五章 指標和陣列

在前面的文章中提到過, 在彙編層面, 所有的資料都存在棧/符號/暫存器中, 棧和符號都屬於記憶體範疇, 進一步抽象就是, 所有的資料都在記憶體和暫存器中(這裡先不討論協處理器或者外部儲存等其他範疇)。 而指標的本質就是記憶體的地址!

5。1 指標和地址

指標通常被認為是c語言中比容易繞暈的一個特性, 而實際上指標的本質十分簡單, 比如如下程式碼:

#include

void empty()

{

int a = 1;

void *b = &a;

}

其彙編形式如下:

。section __TEXT,__text,regular,pure_instructions

。ios_version_min 11, 2

。globl _empty

。p2align 2

_empty: ; @empty

; BB#0:

sub sp, sp, #16 ; =16

add x8, sp, #12 ; =12

orr w9, wzr, #0x1

str w9, [sp, #12]

str x8, [sp]

add sp, sp, #16 ; =16

ret

。subsections_via_symbols

從之前的介紹中知道變數`a`實際上是存放在棧上的, 在這裡是`sp + 12`也就是sp往上12byte的位置, 取地址操作`&a`實際上取到的就是`sp + 12`這個值(也就是`add x8, sp, #12`), 然後將地址從x8放入sp中(`str x8, [sp]`),這裡的`sp`就是變數`b`在棧上的地址。

也就是說, 如果一個變數b裡存放的是另一個變數a的地址, 那麼就可以稱為, b是指向a的指標了!

用匯編的說法來解釋, 就是一個地址指向的記憶體裡面存的內容是另一個地址!

在彙編層面有一個很重要的點就是要對思想做轉變, 在彙編層面沒有變數/物件和指標等概念, 只有暫存器/記憶體和資料, 而資料是沒有型別的(資料只是一個位元序列)! 資料該怎麼理解要看具體的彙編指令是怎麼操作暫存器和記憶體的, 如果使用整數加法指令操作暫存器, 那麼資料將被當做整數, 同理, 如果使用浮點數加法指令操作暫存器那麼資料將被當做浮點數! 如果指令對暫存器做定址操作, 那麼資料將被當做地址(也就值指標)!

5。2 指標和函式引數

在第四章中已經介紹了函式的引數是透過暫存器和棧來傳遞的, 如果我們傳遞的引數是一個整數, 那麼暫存器/棧中存的就是整數, 那如果我們傳的引數是一個指標, 那麼暫存器/棧中存的就是地址! 比如如下程式碼:

#include

void swap(int *px, int *py) {

int temp;

temp = *px;

*px = *py;

*py = temp;

}

int main()

{

int a = 1;

int b = 2;

swap(&a, &b);

return 0;

}

其彙編實現如下(節選):

#呼叫部分

add x0, sp, #8 ; =8

add x1, sp, #4 ; =4

orr w8, wzr, #0x2

orr w9, wzr, #0x1

stur wzr, [x29, #-4]

str w9, [sp, #8]

str w8, [sp, #4]

bl _swap

#實現部分

str x0, [sp, #24]

str x1, [sp, #16]

ldr x0, [sp, #24]

ldr w8, [x0]

str w8, [sp, #12]

ldr x0, [sp, #16]

ldr w8, [x0]

ldr x0, [sp, #24]

str w8, [x0]

ldr w8, [sp, #12]

ldr x0, [sp, #16]

str w8, [x0]

其關鍵流程就是:

在呼叫部分中(main),前兩句`add`指令取變數`a`和`b`的地址放入x0和x1中作為呼叫_swap的引數,後面orr指令和str指令是向地址中存值,這裡的stur這句是無用程式碼可以理解為為了對齊。

在實現部分中(swap),前面兩句str把x0和x1作為px和py引數放入他們在棧上分配的空間裡。然後把x0再撈出來,再取x0作為地址裡存的值放入w8。w8放入`sp + 12`也就是給temp分配的空間。下面就是取px作為地址裡的值,放入py做地址的記憶體空間裡。最後從temp中撈出之前存的py做地址裡存的值放入px做地址的記憶體空間裡。

5。3 指標和陣列

陣列是棧上提前分配好的一片記憶體, 如果你拿到了這片記憶體的地址(指標), 那麼你將可以隨意訪問它。

在日常的使用中, 可能經常會碰見如下兩種訪問陣列的方法, 如`a[1]`和`*(a + 1)`, 它們有什麼區別呢? 看程式碼:

#include

void f1() {

int a[2];

int b = a[1];

}

void f2() {

int a[2];

int b = *(a+1);

}

其彙編實現:

_f1: ; @f1

; BB#0:

sub sp, sp, #48 ; =48

stp x29, x30, [sp, #32] ; 8-byte Folded Spill

add x29, sp, #32 ; =32

adrp x8, ___stack_chk_guard@GOTPAGE

ldr x8, [x8, ___stack_chk_guard@GOTPAGEOFF]

ldr x8, [x8]

adrp x9, ___stack_chk_guard@GOTPAGE

ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF]

ldr x9, [x9]

stur x9, [x29, #-8]

ldr w10, [sp, #20]

str w10, [sp, #12]

adrp x9, ___stack_chk_guard@GOTPAGE

ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF]

ldr x9, [x9]

ldur x11, [x29, #-8]

cmp x9, x11

str x8, [sp] ; 8-byte Folded Spill

b。ne LBB0_2

; BB#1:

ldp x29, x30, [sp, #32] ; 8-byte Folded Reload

add sp, sp, #48 ; =48

ret

LBB0_2:

bl ___stack_chk_fail

。globl _f2

。p2align 2

_f2: ; @f2

; BB#0:

sub sp, sp, #48 ; =48

stp x29, x30, [sp, #32] ; 8-byte Folded Spill

add x29, sp, #32 ; =32

adrp x8, ___stack_chk_guard@GOTPAGE

ldr x8, [x8, ___stack_chk_guard@GOTPAGEOFF]

ldr x8, [x8]

adrp x9, ___stack_chk_guard@GOTPAGE

ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF]

ldr x9, [x9]

stur x9, [x29, #-8]

ldr w10, [sp, #20]

str w10, [sp, #12]

adrp x9, ___stack_chk_guard@GOTPAGE

ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF]

ldr x9, [x9]

ldur x11, [x29, #-8]

cmp x9, x11

str x8, [sp] ; 8-byte Folded Spill

b。ne LBB1_2

; BB#1:

ldp x29, x30, [sp, #32] ; 8-byte Folded Reload

add sp, sp, #48 ; =48

ret

LBB1_2:

bl ___stack_chk_fail

完全一樣有沒有! 其中的關鍵點在於:

從這裡可以推斷出陣列頂部邊界用到的`___stack_chk_guard`的地址是`stur x9, [x29, #-8]`(也就是`sp + 24`),也就意味著陣列的內容是放在`sp + 20`和`sp + 16`裡,既然陣列只有兩個元素,那`sp + 16`這個低地址就是陣列`a`的頭部指標啦。

ldr w10, [sp, #20]

str w10, [sp, #12]

這兩句是`int b = a[1];`的實現, `sp + 20`是陣列頭指標`sp+16`加上偏移量`4`的產物, 而這裡`4`是`sizeof(int)`。

5。4 指標的算術運算

指標(地址)和整數很像, 可以做加減乘除等算術運算, 只不過在做運算的時候, 加減的增量的`1`是代表著一個元素的大小。 比如如下程式碼:

#include

int main()

{

int *a;

a++;

return 0;

}

其彙編實現(關鍵部分):

ldr x9, [sp]

add x9, x9, #4 ; =4

即`a++`的實現, 加的數字是`4`而不是`1`, 因為`int`的大小是`4`。

再比如如下程式碼:

#include

int main()

{

char *a;

a++;

return 0;

}

其彙編實現(關鍵部分):

ldr x9, [sp]

add x9, x9, #1 ; =1

即`a++`的實現, 加的數字是`1`, 因為`char`的大小是`1`。