在做面试题的时候,我遇到了这样一道题:
#include <stdio.h>
void fun()
{
int tmp=10;
int* p=(int*)(*(&tmp+1));
*(p-1)=20;
}
int main()
{
int a=0;
fun();
printf("%d",a);
return 0;
}
一眼看去,a的值好像并未发生改变。但是实际上并不是我们看到的这么简单哦~
正文
在解这道题时,我们需要搞懂函数调用是怎么实现的???
接下来我就来一步一步讲解
函数栈桢地址使用
函数栈桢的使用是由高地址向低地址使用的
函数压栈
函数压栈:在栈顶处不断放入数据
函数调用时使用的寄存器
esp:函数栈顶的地址,随着压栈的进行,esp会向低地址处移动
ebp:函数栈底的地址
在调用main函数之前,还调用了一个函数叫做mainCRTStart()
mainCRTStart是用来调用main函数的一个函数
接下来,我将以下面的代码为例,来讲解函数调用过程
#include <stdio.h>
int Add(int x,int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = Add(a,b);
printf("%d",ret);
return 0;
}
代码分析
调用mainCRTStart()
以调用mainCRTStart()来调用main函数,此时ebp在栈底,esp在栈顶。
用汇编代码来看
注意:函数栈桢图中的序号与步骤号对应,汇编代码前的红色数字与步骤号也是对应的,一个步骤可能执行多条语句
第一步
将ebp的值压在栈顶上(红色方框),esp由esp1移到esp2的位置
第二步
将esp的值赋给ebp,即ebp由ebp1的位置移到ebp2的位置
第三步
esp-4Ch:esp由esp2的位置移到esp3的位置,此时为esp与ebp维护的空间是为main函数开辟了空间
第四步
将ebx、esi、edi依次压入栈中,esp随着每次压栈向低地址处移动,则esp由esp3的位置移到esp4的位置
第五步
将edi放到ebp-4Ch的位置上,即将edi移动到edi1的位置,
将eax的内容拷贝ecx存放内容次,放到edi向下的内容中去。将为main函数开辟的空间全部初始化成随机值
第六步
为ebp-4地址处赋值,为10(a)
第七步
为ebp-8地址处赋值,为20(b)
第八步
为ebp-12地址处赋值,为0(ret)
第九步
将ebp-8地址处的值赋给寄存器eax,并将eax压入栈中(_b)
将ebp-4地址处的值赋给寄存器ecx,并将ecx压入栈中(_a)
将call指令下一条指令的地址压入栈中
该过程进行完,esp由esp4的位置移动到esp5的位置
第十步
进入Add函数中,将main函数的ebp位置压入栈中,esp由esp5位置移到esp5的位置
第十一步
将ebp移动到esp的位置,即将ebp由ebp2的位置移到ebp3的位置
第十二步
esp-44h:为Add函数开辟空间
第十三步
依次将ebx、esi、edi压入栈中,则esp由esp7的位置移到esp8的位置
第十四步
将edi移动到edi1的位置(橙色),并将Add函数开辟的空间初始化为随机值
第十五步
将ebp3-4的位置初始化成0(z)
第十六步
将ebp+8的位置的值(a)放入eax寄存器中
将eax(x)中的值加ebp+0Ch(y),将结果放在eax寄存器中
将eax中的值,赋给ebp-4(z)
第十七步
将ebp-4(z)中的值放入eax寄存器中
第十八步
将edi、esi、ebx弹出栈,将esp由eap8的位置移动到esp7的位置
第十九步
将esp放在ebp的位置,即esp由esp7的位置移动到esp9的位置
第二十步
将ebp弹出,ebp移动至main函数的栈底位置,即esp2的位置
第二十一步
将寄存器eax中存放的值,赋给ebp-0Ch(ret)
面试题解答
其实a的值是在调用函数fun时,通过指针赋值发生的改变
转载请注明原地址:Lucky-pxx的博客 » 点击阅读原文,谢谢!