您的位置:首页 > 其它

C系列总结3 & 剖析函数调用及可变参数--详解栈帧

2017-07-27 00:25 513 查看
- - - - -草稿- - - - -栈帧部分待细化


前言:

不积跬步,无以至千里

栈帧是编程书目中鲜有提及的概念,但其与函数调用细节息息相关,在此做简单总结。

无参考书目

主要参考资料:

互联网

以及

Write by 张鹏霄, zpx736312737@126.com

概要:

定义

调用细节

可变参数函数

定义

易知,函数的调用需要申请,建立栈帧

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构

栈帧对应着一个未运行完的函数,其中保存了该函数的返回地址和局部变量

调用细节

首先,

栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。

通过寄存器ebp(维护栈底),esb(维护栈顶),我们维护一个正在运行的函数的栈帧:



定义一个函数fuc

int fuc()
{
int a = 1;
return a;
}
int main()
{
int num=fuc();
Next command:
getchar();
getchar();
return 0;
}


Vs2017下调试代码转到反汇编

int fuc()
{
001E16A0  push        ebp
001E16A1  mov         ebp,esp
001E16A3  sub         esp,0CCh
001E16A9  push        ebx
001E16AA  push        esi
001E16AB  push        edi
001E16AC  lea         edi,[ebp-0CCh]
001E16B2  mov         ecx,33h
001E16B7  mov         eax,0CCCCCCCCh
001E16BC  rep stos    dword ptr es:[edi]
int a = 1;
001E16BE  mov         dword ptr [a],1
return a;
001E16C5  mov         eax,dword ptr [a]
}


图解汇编代码:(图片来自网络,部分内容与上述代码有出入,不影响意思)

我们将状态分为入栈前和入栈后,在入栈前,main函数作为新栈帧之前的栈帧如下



之后开始入栈,push mov后,将参数与返回地址(开辟一空间记录返回地址,可以理解为代码块Next command:的地址)



移动ebp指向当前栈帧



之后为a开辟空间并记录值,移动esp



之后出栈,将值返回给返回地址的值

可变参数函数的设计

假设我们有一需求,将未知个数字求和。

函数重构的形式

int fuc(int a)
int fuc(int a, int b)
int fuc(int a, int b, int c)
...


显然不合理

易想到,printf参数为可变参数

vs下调取帮助,查看printf函数参数表

int printf(
const char *format [,
argument]...
);


事实上,编译器提供一种语法,实现函数可变参数,以求和函数为例

int(int n,...)//n为参与求和的变量个数,...表示可变参数


求和函数代码如下

#include<stdarg.h>//va_list va_start等的定义
int fuc(const int n,...)
{
va_list arg;//va_list为宏定义,可以建立一个数组
va_start(arg, n);//va_list为宏定义,可以根据第一个参数n的地址找到可变参数第一个元素(联想栈帧的原理)
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += va_arg(arg, int);//va_list为宏定义,访问该元素并将地址跳到下一元素
}
va_end(arg);//va_list为宏定义,出于安全性考虑,将指针指为空
return sum;
}


其中上述涉及到的宏定义在新版vs中多层嵌套,研究vs6.0源代码可得到可读性较强的宏定义如下(可不必研究细节)

#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define va_end(ap) ( ap = (va_list)0 )


综上,结合栈帧原理,我们可以使用可变参数接受未知个数参数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: