我们今天来分析一下函数的本质
知识扩展*:
程序的生成:********** 代码->二进制数据------>程序文件(保存在硬盘中)
程序的运行:********** 程序文件(硬盘)----->加载到内存中
一旦这个程序生成,二进制数据就不会再改变
准备工作
右键属性->C/C++->常规->禁用优化
做实验的时候,分析时可以调成release模式,能减少干扰
查看汇编代码
000511C2 jmp 000514B0
int add(int a,int b)
{
000514B0 push ebp
000514B1 mov ebp,esp
return a + b;
000514B3 mov eax,dword ptr [ebp+8]
000514B6 add eax,dword ptr [ebp+0Ch]
}
000514B9 pop ebp
000514BA ret
int a{ 100 };
000514DC mov dword ptr [ebp-4],64h
int b{ 200 };
000514E3 mov dword ptr [ebp-8],0C8h
int c=add(100,200);
000514EA push 0C8h
000514EF push 64h
000514F1 call 000511C2
000514F6 add esp,8
000514F9 mov dword ptr [ebp-0Ch],eax
第17行 第18行 push100和push200 | 第7行 eax=&[ebp+8] |
第19行 跳转到000511C2 | 第8行 eax=eax+[(ebp+0c)] |
000511C2 跳转到第4行 | 第九行 epb 出栈 |
第4行 push ebp | 第十行 返回000514F6 |
第5行 ebp=esp |
从这里可以看出,ebp+8和ebp+c肯定是a和b的内存空间
函数名就是一个地址,那这个地址代表的内存空间里面存的是什么呢?存的就是代码
证明:我们打印一下函数名
std::cout << std::hex << add;
char* str = (char*)add;
for (int i = 0; i < 30; i++)
{
printf("%02x\n",(unsigned char)str[i]);
}
第一行打印结果为 009811C2,说明函数名是个地址
而之后打印出的字节和汇编代码中函数定义那几行显示的代码字节一样,说明地址里面储存的是代码,既然讲到了内存地址,就和指针有关系
函数指针
指针两要素:地址、类型,类型说明了如何表达这个内存地址
函数指针最重要的两个要素函数返回类型 参数列表
而函数体的内容重要吗?不重要,不管函数体内代码如何,只要确定了返回类型、参数列表就能通过call调用它,而不需要管函数体的具体代码实现
语法 函数返回类型 ( *函数指针变量名 )(参数类型 参数名 , 参数类型 参数名....)
示例:void (*ptr)(int a,int b)
如何使用
int add(int a,int b)
{
return a + b;
}
int main()
{
int (*ptr)(int a, int b) { 0 };
ptr=add ;
std::cout << ptr(1, 2);
}
这里就用ptr指向了add函数,如果指向的函数返回类型不一样怎么办呢?可以用到强制类型转换
char (*ptr)(int a, int b) { (char(*)(int,int)) add };
但是这样写太繁琐了,我们可以用typedef和using来改变
typedef char(*pdef)(int, int);
pdef ptr = pdef add;
注意,这里的pdef是写在了*后面,括号里面,而不是写在最后
using puse = char(*)(int, int);
puse ptr = (puse) add;
函数指针结合结构体
using puse = int(*)(int, int);
struct Role
{
int Hp;
int Mp;
};
int add(Role r)
{
return r.Hp + r.Mp;
}
int main()
{
puse ptr=(puse) add;
std::cout << ptr(1, 2);
}
这里的puse有两个参数,分别对应了struct里面的两个成员类型,重点在倒数第二行,传入的是两个参数,说明ROLE本质就是由两个参数构成
类的函数指针
当写类的函数指针必须把this指针也考虑进去,this指针来源于类的相关信息,所以也要把类的相关信息添加到函数指针
示例:typedef void (类名::*pfunction) (int a,int b)
#include <iostream>
class moveobject
{
public:
void func1()
{
std::cout << 1 << std::endl;
}
void func2()
{
std::cout << 1 << std::endl;
}
};
typedef void (moveobject::* pfunction)();
int main()
{
pfunction pfunction1 = &moveobject::func2;
moveobject* ptr = new moveobject();
(ptr->*pfunction1)();
system("pause");
}
基本用法:用函数指针指向要代表的类成员函数地址,然后调用这个函数指针里的函数
静态成玉函数用法和普通函数一样,因为它没有this指针,而且它就相当于一个普通函数
typedef void ( * pstaticfunc)();
int main()
{
pstaticfunc pfunction = &moveobject::func;
moveobject* ptr = new moveobject();
pstaticfunc();
system("pause");
}