C语言函数
- 函数声明
有声明没实现的函数会出现链接错误 - 函数实现
函数实现和声明可以在一个文件也可以不在一个文件。 - 函数调用
- 实参、形参
形参是声明中占位的参数
实参是调用函数的作者,给的具体的值 - return与返回值
return语句用于函数返回,并带回返回值(如果有)。
对于函数调用的一方,可以将函数调用的返回值直接存储下来,也可以直接放弃。
比如:
C语言的变参函数
集中处理非常朴素的道理,实践以下代码,变参函数的话题。。。
函数调用过程
函数本质
有意义机器码
通过反汇编窗口确认
另外,也可以从打印函数名确认:
void FunTest()
{
printf("hello");
return;
printf("hello, world");
printf("hello, world");
printf("hello, world");
printf("hello, world");
printf("hello, world");
}
void FunValArg(char* arg1, ...)
{
}
void MyAddVal(...)
{
}
int main(int argc, char* argv[])
{
printf("%08X, %08X\r\n", FunValArg, FunTest);
return 0;
}
内存区域的区分技巧
操作系统为了好管理,内存是分区域的。
对于去掉随机基地址的工程,可以通过内存地址,简单区分它属于哪块内存区域。
- 全局区域
一般以0x004x开头,存放:函数的机器码、字符串、全局变量 - 栈区域
一般以0x0018、0x0019、0x0012开头,函数中的局部变量、函数调用过程中的形参都会放在栈中 - 堆区域:暂时不用掌握
实践以下代码加深理解。
int main(int argc, char* argv[])
{
printf("%08X\r\n", "hello");
char chAry[] = "hello";
printf("%08X\r\n", chAry);
char *szHello = "hello";
printf("%08X\r\n", szHello);
return 0;
}
函数调用过程
int MyAdd(int x, int y);
int main(int argc, char* argv[])
{
int nResult = 0;
nResult = MyAdd(10, 20);
return 0;
}
int MyAdd(int x, int y)
{
int nLocal1 = 0x1111;
int nLocal2 = 0x2222;
return x + y;
}
涉及两方:
- 调用方(main)
- 被调用方(MyAdd)
函数的调用及返回的过程,就是在调用方和被调用方切换流程的过程。 又因为函数肩负着接口的作用,所以,除了流程切换之外,还需要保证:(过得去,回得来)
- 调用方传递的参数,可以被被调用方正确的获取
- 被调用方要能够传递出返回值,并且被调用方正确的获取
栈帧的概念
内存区域有一块被划分为栈,所有被调用的函数,都会使用这块区域,但是,他们的局部变量、参数等并不会重叠。
每一次函数被调用,都有特定的一块占内存与这次调用对应,这称为“栈帧”。
开始调用某函数,会自动分配栈帧空间。
如果某函数调用结束,那么会回收栈帧空间,这个过程,称为平衡栈。
函数调用过得去,回得来的细节:
大概轮廓:
- 按照调用约定传参
- 保存返回地址
- 流程转移到被调用函数
- 保存上一层栈帧的地址
- 开辟局部变量空间
- 执行被调用函数的相关代码
- 最终将返回值存放在寄存器eax中
- 返回到调用方
按照调用约定传参
C语言的默认调用约定,具体的操作是:
- 从右往左传参
- 将参数依次push到内存的栈中
- 注意详细讲解:
按照调用约定传参
- 从右往左传参
- 将参数一次push到内存栈中
实际上,C语言调用约定有三种:
- C约定:从右往左传参,通过内存栈区域传参,调用平衡栈(调用方通过汇编,修改esp及存取)
- _stdcall:传参方向和传参介质(内存)都与C约定一致。被调用方平衡栈。
- _fastcall:传参方向从左往右,介质是寄存器+内存,被调用方平衡栈
int _cdecl MyAdd(int x, int y);
int _stdcall MyAdd2(int x, int y);
int main(int argc, char* argv[])
{
int nResult = 0;
nResult = MyAdd(0x10, 0x20);
nResult = MyAdd2(0x10, 0x20);
return 0;
}
int MyAdd(int x, int y)
{
int nLocal1 = 0x1111;
int nLocal2 = 0x2222;
return x + y;
}
int _stdcall MyAdd2(int x, int y)
{
int nLocal1 = 0x1111;
int nLocal2 = 0x2222;
return x + y;
}
节省空间角度而言,_stdcall更优秀