C语言本质

基本概念

我们可以这样去声明一个数组  

  T A[N];     T 是某种类型,比如int, float, double, A是数组名, N是整型常数


这句声明会产生两个动作,

第1,它分配连续L x N byte 的长度,L 是类型T的大小(size).  我们用Xa.表示这段长度的起始地址,

第2,A可以作为一个指针指向这个数组的最开始地方,它的值即为Xa,


数组中的成员可以通过一个index(介于0到N-1)去访问,第I 个成员将会放在Xa(起始地址) + L x i.


举个例子,对于下面这样的声明:

char   a[12];
char   *b[8];
double  c[6];
double *d[5];

 

ArrayElement sizeTotal sizeStart addressElement i
         a                112                 Xa    Xa + i
         b                432                Xb   Xa + 4i
         c                848                Xc   Xa + 8i
        d               420               Xd   Xa + 4i

数组A 包括了12个 char 成员

 数组c 包括了6个double类型,每个需要8 byte。

b 和d 都是数组指针,所以,成员都是4byte 长。

对于IA  32 来说, 内存访问是非常有利于数组访问的,假如E 是一个int 的数组, 我们现在去访问E[i], E 的地址存放在 %edx 里面, i 放在ecx 里面。

那么  指令  :

movl (%edx, %ecx, 4), %eax

将会执行地址计算 Xe+ 4i,读出里面的内容,最后将结果copy到eax里面。

最后这个4, 表示各种数据类型的长度。 可以是 1, 2, 4, 8.

循环

     C语言提供了各种形式的循环, 也就是do-while, while, 以及for.对于这些语句并没有现成的机器码和它们对应。C语言是用 test (检测) 和 jump (跳转)的组合来达到 循环 的效果。对于大多数编译器来说,是基于 do - while 形式的循环来生成机器码。 其他形式的循环是先转化为do - while 然后再编译成机器码。

    我们以do - while 循环为例子,来讲解整个转换的过程, 后续会分析更复杂一点的。

     Do - While 循环

     do-while  循环的一般形式是这样的:    

do
    body-statement
    while (test-expr);


body-statement 会一直执行,直到 test-expr 的值变为0.  注意, body -statement 至少会执行一次。


这种一般形式可以被转化为 条件(conditional) 以及goto 语句,像下面这个样子:


loop:
   body-statement
   t = test-expr;
   if (t)
      goto loop;


也就是说,每次循环去执行body-statement ,然后去计算 test-expr, 如果 test 成立,就回过头过来执行第二次循环。


举个例子, 下面一段代码片去计算参数的factorial (阶乘), 就是n!.  使用的是do-while 循环。( n >0 )

int fact_do(int n)
{
    int result = 1;
    do {
       result += n;
       n = n -1 ;
    } while (n > 1);
    return result;
}


对应的汇编代码是这样的。

1 	.file	"factorial.c"
2 	.text
3 	.globl	fact_do
4 	.type	fact_do, @function
5 fact_do:
6 .LFB0:
7 	.cfi_startproc
8 	movl	4(%esp), %edx     // get n
9 	movl	$1, %eax          // set result = 1
10.L2:
11	imull	%edx, %eax         // 计算 result *= n;
12	subl	$1, %edx            //  n递减1     对应 n = n -1
13	cmpl	$1, %edx             // 比较 n 和 1
14	jg	.L2                  //  如果 > ,跳转到 .L2
15	rep
16	ret
17	.cfi_endproc
18.LFE0:
19	.size	fact_do, .-fact_do
20	.ident	"GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
21	.section	.note.GNU-stack,"",@progbits


寄存器的使用

RegisterVAriableInitially
%eaxresult      1
%edxn       n



上面的汇编代码展示了一个标准的do while 循环,在初始化阶段,用寄存器eax 去存放 result, 寄存器edx 去存放n. 一开始它会去执行 循环体 去更新 n 以及 result.

然后去检测  n 是否大于1. 如果大于1,就跳回到循环最开始处。

我们可以看到 第14 行的这个条件跳转语句是实现循环的主要指令,它决定了是继续循环还是跳出循环。



我们可以看到%eax 被初始化为1.  以及在第11行被更新。 (通常%eax 用来存放函数的返回值)

我们能得出结论, %eax 对应的是程序的result.


while 循环

     while 循环的一般形式是这样子的

 

while (test-expr)
    body-statement


while 循环与do while 循环的差别在于,do - while 循环,循环体至少会执行一次。


有很多种方法将while 循环转化为机器码,一种常规的方法是,转化为do while 循环,需要在必要的时候使用条件分支去跳过循环体的第一次执行。


if (!test-expr)
   goto done;
do
   body-statement
   while (test-expr);
done:


相应的就可以转化为goto 的形式

    t = test-expr;
    if (!t)
        goto done;
loop:
    body-statement
    t = test-expr;
    if (t)
       goto loop;
done:


同样,举个例子,下面代码用while 循环去实现factorial.

int fact_while(int n)
{
    int result = 1;
    while (n > 1) {
        result *= n;
        n = n - 1;
    }
    return result;
}


与它对应的goto版本是这个样子的:


int fact_while_goto(int n)
{
    int result = 1;
    if (n <= 1)
         goto done;
loop:
    result *= n;
    n = n - 1;
    if ( n > 1)
         goto loop;
done:
    return result;
}


对应的汇编语言是这样子的:


	.file	"fact_while.c"
	.text
	.globl	fact_while
	.type	fact_while, @function
fact_while:
.LFB0:
	.cfi_startproc
	movl	4(%esp), %edx
	movl	$1, %eax
	cmpl	$1, %edx
	jle	.L2
.L3:
	imull	%edx, %eax
	subl	$1, %edx
	cmpl	$1, %edx
	jne	.L3
.L2:
	rep
	ret
	.cfi_endproc
.LFE0:
	.size	fact_while, .-fact_while
	.ident	"GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
	.section	.note.GNU-stack,"",@progbits








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值