函数调用背后那点事
来源:互联网 发布:手机蓝牙控制app 源码 编辑:程序博客网 时间:2024/05/22 08:00
当你写下一个简单的C语言程序(比如我们都会写的hello world),你可曾知道这个简单的程序背后的那些事情………今天我们从汇编的角度来浅谈一下一个函数在被调用的前前后后。
我们知道栈保存了一个函数调用所需要的维护信息,而这些维护信息通常被称为堆栈帧或活动记录。堆栈帧一般包括如下几个方面:
1>函数的返回地址和参数。
2>临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
3>保存的上下文:包括在函数调用前后需要保持不变的寄存器。
废话少说来看代码: `
#include<stdio.h>
int sum(int a, int b)
{
int temp = 0;
temp = a + b;
return temp;
}
void main()
{
int a = 10;
int b = 20;
int res = 0;
res = sum(a, b);
printf("res = %d\n", res);
}
下面通过反汇编代码来分析下这些简单的代码:
int a = 10;
009A13DE mov dword ptr [a],0Ah
//将10赋值给局部变量a,以下两行代码一样的效果
int b = 20;
009A13E5 mov dword ptr [b],14h
int res = 0;
009A13EC mov dword ptr [res],0
定义三个局部变量后main函数压栈过程
res = sum(a, b);
009A13F3 mov eax,dword ptr [b]
009A13F6 push eax
//把局部变量b赋给寄存器eax,再把eax压入main函数的栈中
009A13F7 mov ecx,dword ptr [a]
009A13FA push ecx
//把局部变量a赋给寄存器ecx,再把ecx压入main函数的栈中
009A13FB call _sum (09A10FAh)
//调用sum函数,并把call指令下一行指令的地址记录到sum的栈中
009A1400 add esp,8
// 回退形参变量的内存
009A1403 mov dword ptr [res],eax
//把函数的返回值赋给局部变量res
且看sum函数的反汇编代码:
int sum(int a, int b)
{
009A1440 push ebp
//把main函数的栈底指针压进自己的栈中
009A1441 mov ebp,esp
//让当前的栈顶指针赋给sum函数的栈底指针
009A1443 sub esp,0CCh
//开辟一定量的sum函数栈内存
009A1449 push ebx
009A144A push esi
009A144B push edi
//把以上三个寄存器压入sum函数的栈中
009A144C lea edi,[ebp-0CCh]
//把当前的栈顶指针赋值给edi
009A1452 mov ecx,33h
009A1457 mov eax,0CCCCCCCCh
009A145C rep stos dword ptr es:[edi]
//相当于一个循环拷贝动作,把以上sum开辟的内存初始化为0CCCCCCCCh
int temp = 0;
009A145E mov dword ptr [temp],0
temp = a + b;
009A1465 mov eax,dword ptr [a]
009A1468 add eax,dword ptr [b]
009A146B mov dword ptr [temp],eax
//以上三句先将a的值放到eax中,再将b的值和a的值相加和放到eax中
return temp;
009A146E mov eax,dword ptr [temp]
//把temp的值赋值给eax寄存器
}
009A1471 pop edi
009A1472 pop esi
009A1473 pop ebx
//三个寄存器出栈
009A1474 mov esp,ebp
//回退sum函数的内存栈
009A1476 pop ebp
//出栈,回退到main函数中
009A1477 ret
//让esp出栈,并把下一行指令的地址赋值给cpu的pc寄存器
整个内存栈布局如下:
至此,整个sum函数的调用过程结束!!
以上sum函数返回值为4字节(32位机器)大小,我们发现是通过eax寄存器带回的,对于c语言的的内置类型、结构体、联合体(union)、枚举类型(enum)函数的返回值如果小于4字节,其由寄存器eax带回。
如果是8个字节则是由eax和edx共同带回。
如果它大于8个字节则在调用函数的时候会先压入(先压参数,后压临时量)main函数栈上的一块临时内存的地址,然后在调用函数return时会把要返回的值拷贝到main函数那块临时内存上,并通过eax寄存器返回临时内存的地址。
再看看最后的函数调用约定吧
调用约定主要有以下:
- _cdecl c调用约定
- _stdcall windows标准的调用约定
- _fastcall 快速调用约定
- _thiscall C++成员函数的调用约定
- //_pascal (调用顺序从左向右,其他都是从右向左)
函数的调用约定会影响:
- 函数产生的符号名字不同
- 函数参数入栈顺序不同
- 谁来清理新参的内存
第一个影响很好理解,如果采取的调用约定不同则,在编译时产生的函数符号不同,则会影响程序的链接,可能会导致链接的失败。
第二个影响是参数入栈顺序,除了以上的_pascal 调用外其他入栈顺序都是从右向左。
第三个影响归结如下:
_cdecl:调用方开辟形参内存,调用方释放新参内存
_stdcall:调用方开辟形参内存,被调用方自己释放新参内存
_fastcall :调用方开辟形参内存,但是把最左边(也就是最后8字节的实参通过寄存器带到被调用函数当中),被调用方自己释放新参内存
好累,睡觉。。
- 函数调用背后那点事
- 委托背后那点事
- 超哥背后的那点事
- 调用HttpClient的那点事
- 程序的内存布局——函数调用栈的那点事
- 程序的内存布局——函数调用栈的那点事
- 程序的内存布局——函数调用栈的那点事
- 程序的内存布局——函数调用栈的那点事
- silverlight那点事:一,silverlight调用ocx控件
- 析构函数和Finalize()之间的那点事
- JavaScript学习笔记(05)之函数的那点事
- C++中构造函数的那点事
- 虚函数与动态绑定的那点事
- 派生、虚函数那点事(未完)
- 关于C#静态构造函数那点事
- 面试那点事
- 公司那点事
- 编程那点事!!
- 为CSDN博客添加站内搜索栏目
- 运算符
- python爬取晋江手机界面的链接
- S3C2440 cp15协处理器详解
- 慕课网学习spring入门篇-AOP基本概念
- 函数调用背后那点事
- poj 1815 Friendship 求字典序最小的最小割
- 2017.7.18 学习记录 JSTL初学
- Python中__repr__和__str__区别
- C++使用数组时,error C2105 “++需要左值”问题处置
- C++数组地址偏移
- 判断字符串是文本还是二进制
- git 的用法
- 小数在计算机中的表示