语言函数可变长参数

604 篇文章 8 订阅
579 篇文章 5 订阅

可变长参数:顾名思义,就是函数的参数长度(数量)是可变的。比如 C 语言的 printf 系列的(格式化输入输出等)函数,都是参数可变的。下面是 printf 函数的声明:

int printf ( const char * format, ... );

可变参数函数声明方式都是类似的。

1.2 如何实现

C语言可变参数通过三个宏(va_start、va_end、va_arg)和一个类型(va_list)实现的,

void va_start ( va_list ap, paramN );
参数:
ap: 可变参数列表地址
paramN: 确定的参数
功能:初始化可变参数列表(把函数在 paramN 之后的参数地址放到 ap 中)。

void va_end ( va_list ap );
功能:关闭初始化列表(将 ap 置空)。

type va_arg ( va_list ap, type );
功能:返回下一个参数的值。

va_list :存储参数的类型信息。

实现C语言可变长参数函数:用 va_start 获取参数列表(的地址)存储到 ap 中,用 va_arg 逐个获取值,最后用 va_arg 将 ap 置空。


-----------------------------------------------我是分割线---------------------------------------------------------------------------

以下源码,来自“..\Microsoft Visual Studio 10.0\VC\include”

1
2
3
4
5
6
7
8
9
10
11
// stdarg.h
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
// vadefs.h
typedef char va_list;
#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)      ( ap = (va_list)0 )
#define _ADDRESSOF(v)   ( &(v) )
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

既然 sizeof 和 _INTSIZEOF 值一样,为什么不直接用 sizeof 呢?干嘛要写的那么复杂?答案是为了字节对齐(无论32位还是64位机器,sizeof(int)永远代表机器的位数,明白了吧!^_^)

现在再去看变长参数的实现:其实就是把参数在栈中的地址记录到 ap 中(通过一个确定参数 paramN 确定地址),然后逐个读取值。


-------------------------------------------------------------我是分割线----------------------------------------------------------------

简单介绍两种函数调用约定

__stdcall (C++默认)

  1. 参数从右向左压入堆栈
  2. 函数被调用者修改堆栈
  3. 函数名(在编译器这个层次)自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

__cdecl (C语言默认)

  1. 参数从右向左压入堆栈
  2. 参数由调用者清楚,手动清栈,被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

那么,变参函数的调用方式为(也只能是):__cdecl 。

----------------------------------------------------------我是分割线------------------------------------------------------------------

stdarg.hANSI C 的标准头文件。它对可变参数的函数(vararg function)提供了支持。

stdarg.hvararg function支持的关键是定义了几个非常有用的宏。注:下面说道的程序运行栈不是很精确,应该是进程的运行栈或进程的执行栈。

1.typedef char *va_list;

注解:本质是一个指向程序运行栈中某一个地址的指针(以后提到的栈都为程序运行栈)

2.#define  __va_size(type) /

(((sizeof(type) + sizeof(long) - 1) / sizeof(long)) */ sizeof(long))

注解:计算某种数据类型的参数在栈中占有的空间。/是连接宏定义中的两行:如

                        #define sum(a,b)((a)+(b))

           等效于:

                        #define sum(a,b)((/

                        a)+(b))

                  

IA32(32位机器程序或汇编程序)的程序中,PUSHPOP指令的操作数是4Bytes(DWORD)。如:char变量为一个字节,但入栈时需要一个DWORD。一个

sizeof5Bytes的结构变量需要2DWORD

3#define     va_start(ap, last) /

((ap) = (va_list)&(last) + __va_size(last))

 

注解:让直指指向第一个可变参数的首地址。

4.  #define    va_arg(ap, type) /

        (*(type *)((ap) += __va_size(type), (ap) -/ __va_size(type)))

注解:这是我见过用逗号操作数最巧妙地例子。

5.  #define    va_end(ap)   ((void)0)

注解:出现一个空值 标准库里将指向可变参数的指针重置为NULL

 

为了详细解析,我先说一下几个要点。

1.     函数参数的传递机制:这里不是指值传递和引用传递。而是以从汇编(机器)语言程序员看参数传递。

我们知道有三种传递方式:寄存器传值,存储器传值,堆栈传值。C支持那种参数传值方式呢?C程序员怎么选择函数参数传递方式呢?在C语言的标准引入了一个概念:调用规则(calling convention),调用规则有:_cdecl,_stdcall,_fastcall,_thiscall(仅C++支持)等,请大家参阅MSDN获得详情。_cdecl 默认C/C++的调用规则,_stdcall win32API的调用规则,_fastcall一般不用,_thiscall C++的类成员函数的调用规则。详细情况见下表:

1:函数调用规则详解

调用规则

清栈

是否支持vararg function

参数传递

_cdecl

调用者(caller)

支持

函数参数从右向左入栈

_stdcall

被调用者(callee)

函数参数从右向左入栈

_thiscall

被调用者(callee)

函数参数从右向左入栈,最后压入默认参数this

_fastcall

被调用者(callee)

前两个参数存入寄存器ECXEDX,剩余参数从右向左入栈

 

 

函数的调用过程:用户函数的调用是由程序运行栈来管理的,

标准库函数和系统调用一般也会用到栈,还有共享代码段 (dll),陷入(软中断)等概念,我就不详细说了。现在只要知道用户函数是由程序运行栈管理的即可,栈的功能体现在三点:1.用栈保存函数的返回地址,2.用栈传递函数参数,3.在栈中创建局部变量。这里有个概念叫栈帧,它是指函数在调用时占用的栈中一部分连续的空间。函数的嵌套反映在栈中就是调用函数的栈帧的地址高于被调用者的栈帧的地址并顺序存放(假设栈是从高向低增长的)。函数的返回会伴随着他的栈帧的销毁,这也是为什么局部变量会在作用域之外没法应用。上面的表中有一列为“清栈”,想必你读到此处,它的含义你明白了:调用者清栈,是指被调用的函数参数保存在它调用者的栈帧中;而被调用者清栈,是指被调用的函数参数保存在它自己的栈帧中,通常用RET n指令返回。

       几点需要注意

      1.     有可变参数的函数必须拥有非可变参数,并且非可变参数必须位于可变参数的前面。

 2.     程序员必须自己控制可变参数的类型以及可变参数的数目。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值