從彙編角度理解C語言(六)【完】 原

先介紹下數相關

例1

static void Apple(void)
{
    int i;
    short scores[4];
    scores[i] = 10;
}

可以推算出其共佔有16字節的存儲空間:

生成的彙編代碼如
SP = SP - 12 ; make space for locals (left uninitialized)
R1 = M[SP + 8] ; load value of i into R1
R2 = R1 * 2 ; multiply i by size of element (short = 2 bytes)
R3 = SP + R2 ; add offset to base address of scores array
M[R3] =.2 10 ; assign array element, copy only 2 bytes!
SP = SP + 12 ; clean up stack
RET ; return to caller

但是對於細心的你可能會發現,此時的i是個不確定值,可能引起數組越界,所以此段你碼是危險的,然而對於C語言編譯器來說,它纔不會關心這些。


對於數組作爲函數參數傳遞呢 ?
例2

static void Banana(short scores[], int n)
{
   scores[n] = 10;
}

其實它總共需要12個字節的存儲空間,如下圖所示:



其生成的彙編如下
; no locals, so no need to make space
R1 = M[SP + 8] ; load n into R1
R2 = R1 * 2 ; multiply n by size of element (short = 2 bytes)
R3 = M[SP + 4] ; load base address of scores array
R4 = R3 + R2 ; add offset to base address
M[R4] =.2 10 ; assign array element, copy only 2 bytes!
RET;


其實函數中只存儲了數組的起始地址,並沒有把整個數組傳遞進來,所以對於下面幾種函數定義:

static void Banana(short scores[], int n)
static void Banana(short *scores, int n)
static void Banana(short scores[10], int n)

例2生成的彙編代碼是一樣的。

但若要將一個確定大小的數組作爲參數傳遞,有一個隱式方法,如下代碼
例3

static void Apple(void)
{
    int i;
    short scores[4];
    Banana(scores, i);
}

生成的彙編如下
SP = SP - 12 ; make space for locals (left uninitialized)
R1 = M[SP + 8] ; load i into R1
R2 = SP ; load base address of scores array
SP = SP - 8 ; make space for params to Banana function
M[SP + 4] = R1 ; assign second param
M[SP] = R2 ; assign first param
CALL <Banana> ; jump to Banana function
SP = SP + 8 ; clean up parameter block from Banana
SP = SP + 12 ; clean up locals
RET;

他的可執行代碼在棧中存儲形式如下圖:


對於指針傳遞呢?以數據交換函數來示例下,

如下代碼
例4

void swap(int *ap , int *bp)
{
    int temp = *ap;
    *ap = *bp;
    *bp = temp;
}

其生成的彙編如下
SP = SP - 4 ;
R1 = M[SP + 8] ;
R2 = M[R1];
M[SP] = R2 ;

R1=M[SP + 12]; load the address of bp into R1
R2 = M[R1];
R3 = M[SP + 8] ;
M[R3] = R2;

R1 = M[SP];
R2=M[SP + 12]; 
M[R2] = R1;
SP = SP + 4; 
RET;

由此可見指針傳遞時每次都要先取出數據所在地址,再根據地址找到對應的數。

下面介紹一下函數指針,程序只會根據傳遞過來的函數地址,跳到對應的地方執行指令,而不會關心函數具體名字是什麼。
可根據如下示例看出:
例5

typedef int (*compareFn)(const void *, const void *);

static bool AreEqual(const void *a, const void *b, compareFn cmp)
{
    if (cmp(a, b) == 0) // not the most compact way to write this
        return true; // but simplest to generate code for
    else
        return false;
}


彙編代碼:
; no locals, so no space created
R1 = M[SP + 8] ; load b into R1
R2 = M[SP + 4] ; load a into R2
R3 = M[SP + 12] ; load cmp into R3
SP = SP - 8 ; make space for params of the fn
M[SP + 4] = R1 ; assign second param
M[SP] = R2 ; assign first param
CALL R3 ; call to address stored in R3
SP = SP + 8 ; remove parameters when function returns
BNE RV, 0, PC + 12 ; if return value not zero jump to else
RV = 1 ; assign return value to true
RET ; return to caller
RV = 0 ; assign return value to false
RET ; return to caller


下面再給一具體應用示例
例6

int CompareStrings(const void *a, const void *b)
{
    return strcmp((char *)a, (char *)b);
}

static void Caller(void)
{
    int same;
    char *s, *t;
    same = AreEqual(s, t, CompareStrings);
}



其生成的彙編:
SP = SP - 12 ; create space for locals (left uninitialized)
R1 = M[SP] ; load t into R1
R2 = M[SP + 4] ; load s into R2
R3 = <CompareStrings> ; load address of CompareStrings fn into R3
SP = SP - 12 ; make space for params of AreEqual
M[SP + 8] = R3 ; assign third param (the function pointer)
M[SP + 4] = R1 ; assign second param
M[SP] = R2 ; assign first param
CALL <AreEqual> ; call to AreEqual
SP = SP + 12 ; remove parameters when function returns
M[SP + 8] = RV ; assign return value to local variable same
SP = SP + 12 ; clean up space used for locals
RET ; return to caller


要注意,對於要傳遞的函數指針參數與原定義的函數指針相匹配是非常重要的,否則如果傳遞的不匹配或者傳遞的爲NULL則可能會使程序跑飛或者程序崩潰。

再給出一複雜的示例
例7

struct person {
   int age, id;
   struct person *next;
};
static char Muppets(struct person bert, struct person *ernie)
{
     struct person **oscar;
    ((struct fraction *)bert.next)->denominator = 0;
    ernie = &bert;
    oscar = &ernie;
    (**oscar).next = ernie;
    return bert.age;
}

生成彙編如下:
;As always, begin by making space for the locals:
SP = SP - 4 ;
make space for oscar
;Now, consider the code generated for the first line of C:
R1 = M[SP + 16] ; 
 retrieve  bert.next
; now that bert.next is safely tucked
; into R1, pretend that R1 points
; to a struct fraction
M[R1 + 4] = 0 ;
note the offset of four needed to
; access the denominator field of fraction
;On to the next two lines of assignments:
R1 = SP + 20 ;
compute address of ernie
R2 = SP + 8 ;
compute base address of bert
M[SP + 20] = R2 ;
store bert's address in ernie
M[SP] = R1 ;
store ernie's address in oscar
;Now for the next line... Deep breath.
R1 = M[SP] ; l
oad oscar, R1 now contains a struct
; person **
R1 = M[R1] ;
deref, now R1 contains a struct person  *
R2 = M[SP + 20] ;
store ernie in a register
M[R1 + 8] = R2 ;
if R1 stores the address of a struct
; person, then R1+8 is the address of
; that struct's next field.  We store
; to that field.
;And finally, the return statement:
R3 =  M[SP + 8] ;
load bert.age  field
RV =.1 R3 ;
copy lowest byte into return value
SP = SP + 4 ;
clean up space used for locals
RET ;
return control to caller

下面是一個遞歸函數的示例
例8

int factorial(int n)
{
    if(0 == n)
    return 1;
    else 
    return n * factorial(n - 1);
}

生成的彙編爲:
<fact>
R1 = M[SP + 4]; 
BNE R1 , 0 , PC + 12 ;
RV = 1;
RET;

R1 = M[SP + 4];
R1 = R1 - 1;
SP = SP - 4;
M[SP] = R1 ;
CALL <fact>;
SP = SP + 4;
R1 = M[SP + 4];
RV = RV * R1;
RET;


至此這一系列就介紹完了。

對於程序員的你還需在實踐中加深理解,並結合經典教材去深入探索,相信會融會貫通 ,共勉


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章