引言
最近无聊看了看征服C指针,看到函数的不定参数时想起在这方面还没有做过一些总结,只是略微了解一些,意识到其实完全不需要借用va_list,va_start,va_arg这些标准函数也可以自己实现操作,具体我们来看看。
函数栈
首先我们需要了解一下linux下一个进程的内存地址空间是如何布局的,在linux中,0~3G的虚拟地址为进程所有,3G~4G由内核所使用,每一个进程都有自己独立的0~3G内存地址空间。当进程进行函数调用时,我们都知道传入被调用函数的参数是通过栈进行操作的,这里我们只需要简单了解一下linux的内存地址空间中的栈是自顶向下生长的,就是栈底出于高地址处,栈顶出于低地址处。
好的,简单了解了内存地址空间的栈后,我们还需要简单了解一下EBP和ESP这两个寄存器,EBP是用保存栈低地址的,而ESP用于保存栈顶地址,而每一次函数调用会涉及到一个栈帧,
举个实例详细说明一下一个函数帧的特点,比如
1 /* B被A调用 2 * 参数:data1, data2, data3 3 * 局部变量: s1, s2, s3 */ 4 void B (int data1, int data2, int data3) 5 { 6 int b_s1; 7 int b_s2; 8 int b_s3; 9 } 10 11 /* A调用B函数 */ 12 void A (void) 13 { 14 int a_s1; 15 int a_s2; 16 int a_s3; 17 18 B (1, 2, 3); 19 printf ("1\n"); 20 }
在以上例子中栈帧情况如下图所示
从图例中可以看出,当A函数没有调用B函数时,A函数的栈帧只保存着局部变量,而EBP(栈底指针)指向的是A函数的函数栈帧头,而当A函数调用B函数时,A函数会将B函数所需要的参数从右往左压入栈(在例子中先压入3,之后是2,最后是1),之后会将A调用完B之后所需要运行的第一条指令压入栈,此时建立一个B的栈帧,具体流程:
- 从右往左将B函数所需参数压入栈
- 压入执行完B函数之后的第一条指令地址
- 建立B栈帧
- 压入A栈帧的栈底
- 压入B函数保护的寄存器
- 压入B函数的局部变量
时间: 2024-10-18 18:17:28