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

- - - - -草稿- - - - -栈帧部分待细化

前言:

不积跬步,无以至千里
栈帧是编程书目中鲜有提及的概念,但其与函数调用细节息息相关,在此做简单总结。
无参考书目
主要参考资料:

  • 互联网

以及

  • 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 )

综上,结合栈帧原理,我们可以使用可变参数接受未知个数参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值