基本概念
我们可以这样去声明一个数组
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];
Array | Element size | Total size | Start address | Element i |
---|---|---|---|---|
a | 1 | 12 | Xa | Xa + i |
b | 4 | 32 | Xb | Xa + 4i |
c | 8 | 48 | Xc | Xa + 8i |
d | 4 | 20 | 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
寄存器的使用
Register | VAriable | Initially |
%eax | result | 1 |
%edx | n | 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