计算机程序的运行
本文将通过一个C语言的程序来介绍以下几个概念:
- 汇编语言
- 函数调用
- 计算机的运行过程
首先,我们先写一个简单的C语言程序,如下:
int g(int x)
{
return x +100;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(200) + 300;
}
然后我们把源程序编译成一个汇编语言,指令如下
gcc -S -o main.s main.c -m32
这样,我们就得到一个汇编文件,为如下截图:
为了便于理解,我们把一些为了方便编译器工作的代码给删除,结果如下:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $100, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $200, (%esp)
call f
addl $300, %eax
leave
ret
接下来,我们将以这个文件来说明上面说的几个概念
汇编语言
汇编语言是一种低级语言,接近机器指令,但还是非常方便,效率强大。汇编语言有2种语言,一种是Intel语法,一种是AT&T格式,Linux系统中一般都用的是AT&T语法。AT&T语法的一个特点就是源操作数在前,目的操作数在后,恰好和Intel语法相反,常见的汇编指令有:
- mov:传送指令
- push和pop:压栈和出栈指令
- call和ret:后面会说到
- sub和add:减法和加法指令
函数的过程调用
程序使用程序栈来支持过程调用,用栈来传递过程参数,存储返回信息等等。程序为每个过程分配的那部分栈称为栈帧。
一般一个过程调用的前两句汇编代码为:
pushl %ebp
movl %esp,%ebp
进行栈帧信息的存储,方便过程调用结束后返回。
过程调用结束之前的最后两句代码一般为:
leave //等价于这两句代码 movl %ebp,%esp
//popl %ebp
ret
这样就顺利完成了一个函数的调用及返回。其他的比如参数的传递是通过在调用之前进行压栈传递的,如在示例代码中,main调用f是这么做的
subl $4, %esp
movl $200, (%esp)
call f
先把esp减4,再把200放到esp所指向的位置,其实就相当于直接把200压栈了。然后call f 调用f函数。基本上一个过程调用就是先压参数,调用,保存栈帧,然后返回的。
计算机程序的运行
现在的计算机都是冯诺一曼结构,其核心就是存储程序思想,凡是图灵机所能做的事情,计算机就可以做。当然,凡是图灵机不能做的事情,不管是现在还是未来的计算机都不能做。计算机是机器,它只认识二进制01,用这些一串串的01构成了一条条指令,它有个eip寄存器,计算机就把这个寄存器所指向位置的指令取出来,然后一条条的执行,然后完成了我们所要做的任务。