在日常的代码生活中,函数总是与我们形影不离,但是我们是否有想过,函数到底是如何在内存中创建存放的呢?作为一名优秀的码农,我们万万不可知其然而不知其所以然,如果想要更好的掌握理解所学知识,我们对知识应该有一个更深层次理解,所以接下来来了解一下有关函数的创建与销毁问题
在正式开始之前,还需要了解几个知识:
寄存器
常使用的寄存器有很多种,例如:eax ebx ecx edx ebp esp等等
今天与内容有关的主要就是 ebp esp,这两个寄存器中存放的是地址
而这两个地址用来维护函数栈帧(类似两个保安看守一个小区)
内存
而函数以及函数的临时变量(形参)就是存放在栈区
部分汇编指令
push----PUSH 指令首先减少 ESP 的值,再将源操作数复制到堆栈。操作数是 16 位的,则 ESP 减 2,操作数是 32 位的,则 ESP 减 4(将对象进行压栈);
mov ----MOV 指令将源操作数复制到目的操作数;
sub ----两个操作数的相减,即从A中减去B,其结果放在A中;
lea ----LEA指令将存储器操作数mem的4位16进制偏移地址送到指定的寄存器;
pop ----将操作对象弹出栈帧,出栈
call ----调用函数,调用前会将call下一条语句的地址压栈在栈顶
ret ----将栈顶的地址弹出并返回到该地址的地方
ok,了解完毕,正式进入主题
压栈:给栈顶放一个元素(push)
出栈:从栈顶删除一个元素(pop)
在每一个函数运行前,系统都需要为函数以及函数的临时变量在栈区中开辟空间,而这块空间也就是叫做函数的函数栈帧
这就需要寄存器esp和ebp来进行管理了:
ebp存放它的高地址,esp存放它的低地址,这两个地址之间的内存就形成了当前函数的函数栈帧,每当需要调用一个新的函数esp和ebp就会去维护那个新的函数栈帧,两个寄存器里面就会存放两个新的地址
其实,main函数也是一个会被内置函数调用的函数,从调试中我们可以看出
main函数被一个叫做mainCRTStartup的函数所调用,所以我们用画图的方式来表达是这样的
一开始两个指针指向管理的是mainCRTStartup函数的函数栈帧,该函数调用main函数时,从调制中的汇编指令来看看这两个指针是如何操作的
通过压栈的一系列操之后的内存示意图如下:
但还没完,还有下面的操作
执行到这,为main函数空间的开辟才正式结束,示意图如下
继续执行代码指令
存放变量后如下图所示
变量不初始化打印随机值的原因,也正是因为在开辟函数栈帧是赋值为CCCCCCCCC,在不同的编译器下就会编译出不同的值
执行完变量初始化指令后正式调用Add函数
接下来是传参操作
示意图如下
进入Add函数
示意图如下
到这里就执行完Add函数
接下来是销毁操作
最终示意图如下
最后main函数栈帧的销毁过程也和add函数的一样,就不再过多介绍了(真的很累画这个图)
下面发散思考几个问题
1.局部变量是怎么创建的?
函数栈帧创建后编译器分配由高到低地址创建变量;
2.为什么局部变量不初始化的值是随机值?
函数栈帧创建后会默认将所有内容初始化为0CCCCCCCCCCh;
3.函数是怎么传参的?传参的顺序是怎么样的?
传参是将实参值拷贝后进行压栈在栈顶,顺序是由右到左;
4.形参和实参是什么关系?
形参是实参的临时拷贝,只是值相同却是不同的地址;