从汇编角度理解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;


至此这一系列就介绍完了。

对于程序员的你还需在实践中加深理解,并结合经典教材去深入探索,相信会融会贯通 ,共勉


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