有人把程序员比作魔术师,几行简单的代码就能指示电脑作出各种各样不同的操作,作为一个合格的魔术师,只有一步一步掌握魔法的底层运行原理,才能不断创新,创造出更新,效率更高的魔法。在计算机科学中,函数是一个非常重要概念,与此相关的栈,栈如此重要,以至于CPU从底层硬件上实现了它,下面我试着分析一下函数的调用和数据栈,水平有限,如有疏漏和错误欢迎指出。
系统的硬件组成
现代计算机遵守冯·诺依曼体系结构,一般来说由CPU(中央处理器),主存储器(内存),I/O总线和各种外设组成,如下:
一个程序能运行,涉及到CPU和内存两个主要设备,而CPU通过寄存器(Register)对内存进行操作(读写),Intel IA32架构下,主要寄存器如下:
寄存器不仅可以存储数据,还可以通过它进行内存的操作,如:
movl $0x7c00,%eax ;在寄存器中存储16进制的7c00
movl $45,(%eax) ;把45存放到%eax存储中数据所指向的内存中去
内存中的栈
对于现代分时操作系统来说,程序运行时“独占内存”,对于x86架构来说,进程可访问的内存空间如下:
上图中从上往下第四项为进程栈空间,栈以栈帧为单位,为每个进程创建独立的栈帧,寄存器ebp和esp存的值分别指向当前栈帧的栈底和栈顶。
栈帧从高地址到低地址存放:局部变量,调用的其它函数的参数,返回地址,下一个栈帧的栈底。
用汇编语言编写一个add函数:
.global add .type add,@function add: push %ebp movl %esp,%ebp movl $0,%eax addl 8(%ebp),%eax addl 12(%ebp),%eax pop %ebp ret
编译成libadd.so文件:
gcc add.s -fPIC -shared -o libadd.so
用C语言编写文件:
#include <stdio.h> int main(){ int a,b; printf("Please Input to numbers:"); scanf("%d %d",&a,&b); int c = add(a,b); printf("a+b=%d\n",c); return 0; }
编译并连接文件:
gcc main.c -o main -ladd -L.
运行main文件:
[root@vultr ~]# ./main
Please Input to numbers:25 100
a+b=125
add函数运行前后,数据栈的变化如下(大端):
参考资料
- Randal E. Bryant 深入理解计算机系统
- 深入理解C语言的函数调用过程
评论列表: