C语言支持定义可变参数的函数,方法是在函数的参数列表最后加上 " ... ",代表变长的参数列表,例如:

void Func(int num, ...) {  }

需要注意 “...” 必须在最后,而且前面起码要有一个固定的参数,类型可以任意。

为什么要有一个固定的参数呢?这篇文章要说明的就是这个问题。

首先我们是如何调用变长参数列表里的变量?

需要使用 stdarg.h 里定义的三个宏:va_start(ap, x)、va_arg(ap,t)、va_end(ap),还有一个va_list类型(本质上是字节指针)

这几个宏的源代码:

  typedef char* va_list;

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

  #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))

va_start用于获取变长参数列表的起始地址。

使用方法是:

  1. 定义一个va_list类型变量,例如vlist.
  2. 使用宏 va_start(vlist, 最后一个固定参数) 获取变长列表的起始地址
va_list vlist;
vlist = va_start(vlist, num);

这个宏本质上是获取固定参数(如num)的下一个参数地址。原理是调用函数时,程序会将函数参数逐个压入栈中,使参数连续排列在内存中,因此只需要知道上一参数的内存地址和它的类型,就可以算出下一参数的地址。

因此这个宏等价于:vlist = (char*)&num + sizeof(num);

va_arg用于按顺序获取下一个参数。

使用方法:

Type value = va_arg(vlist, Type);

本质上是对变长参数列表指针加sizeof(Type),返回累加前的地址指向的值。等价于:

Type value = *(Type*)vlist;
vlist += sizeof(Type);

va_end非常简单,就是把变长参数列表的指针置0,防止可能的错误。等价于:

vlist = (char*);

最后的简单总结:

之所以要有一个固定参数,是因为只有知道最后一个参数的地址,才能获取变长列表开始的地址。

此外需要注意的是,在不同平台,不同编译器里,由于内存排列有所差别(内存对齐的差别),实际情况不一定有上面写的等效代码一样简单。具体可以查看vadefs.h里的定义。

 #ifdef __cplusplus
#define _ADDRESSOF(v) (&const_cast<char&>(reinterpret_cast<const volatile char&>(v)))
#else
#define _ADDRESSOF(v) (&(v))
#endif #if (defined _M_ARM || defined _M_HYBRID_X86_ARM64) && !defined _M_CEE_PURE
#define _VA_ALIGN 4
#define _SLOTSIZEOF(t) ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1))
#define _APALIGN(t,ap) (((va_list)0 - (ap)) & (__alignof(t) - 1))
#elif defined _M_ARM64 && !defined _M_CEE_PURE
#define _VA_ALIGN 8
#define _SLOTSIZEOF(t) ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1))
#define _APALIGN(t,ap) (((va_list)0 - (ap)) & (__alignof(t) - 1))
#else
#define _SLOTSIZEOF(t) (sizeof(t))
#define _APALIGN(t,ap) (__alignof(t))
#endif #if defined _M_CEE_PURE || (defined _M_CEE && !defined _M_ARM && !defined _M_ARM64) void __cdecl __va_start(va_list*, ...);
void* __cdecl __va_arg(va_list*, ...);
void __cdecl __va_end(va_list*); #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) (*(t *)__va_arg(&ap, _SLOTSIZEOF(t), _APALIGN(t,ap), (t*)0))
#define __crt_va_end(ap) ((void)(__va_end(&ap))) #elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) #elif defined _M_ARM #ifdef __cplusplus
void __cdecl __va_start(va_list*, ...);
#define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v))))
#else
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v)))
#endif #define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) #elif defined _M_HYBRID_X86_ARM64
void __cdecl __va_start(va_list*, ...);
#define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) #elif defined _M_ARM64 void __cdecl __va_start(va_list*, ...); #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) \
((sizeof(t) > ( * sizeof(__int64))) \
? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \
: *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) #elif defined _M_X64 void __cdecl __va_start(va_list* , ...); #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))
#define __crt_va_arg(ap, t) \
((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - )) != ) \
? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \
: *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) #endif

vadefs.h 部分代码

知道了原理,我们其实可以直接获取变长参数列表里任意一个变量,而不用逐个获取,特别是在参数的类型都相同的情况下,例如:

 int Sum(int count, ...)
{
int sum = ; for (int i = ; i < count; i++)
{
sum += *(int *)((char *)&count + sizeof(int) * (i + ));
} return sum;
}

当然,这样的代码移植性差,如果更改了平台很可能就会出错,使用时还是谨慎为好。

此外还有一些陷阱:

https://blog.csdn.net/smstong/article/details/50751121

C 可变参数函数的本质的更多相关文章

  1. C语言学习020:可变参数函数

    顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...

  2. Swift开发第十篇——可变参数函数&初始化方法顺序

    本篇分为两部分: 一.Swift中的可变参数函数 二.初始化方法的顺序 一.Swift中的可变参数函数 可变参数函数指的是可以接受任意多个参数的函数,在 OC 中,拼接字符串的函数就属于可变参数函数 ...

  3. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  4. C可变参数函数 实现

    转自:http://blog.csdn.net/weiwangchao_/article/details/4857567 C函数要在程序中用到以下这些宏: void va_start( va_list ...

  5. C语言可变参数函数实现原理

    一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...

  6. c可变参数函数

    C函数要在程序中用到以下这些宏: <pre lang="c" escaped="true">void va_start( va_list arg_p ...

  7. 【转】C/C++中可变参数函数的实现

    转自:http://www.cnblogs.com/cylee025/archive/2011/05/23/2054792.html 在C语言的stdarg.h头文件中提供了三个函数va_start, ...

  8. PHP中的可变参数函数和可选参数函数

    1)可选参数函数.例如: <?phpfunction add($var1,$var2,$var3=0,$var4=0){ return$var1+$var2+$var3+$var4;}echo ...

  9. C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...

随机推荐

  1. Windows Server 搭建企业无线认证(Radius认证方案)

    认证协议介绍: 扩展认证协议EAP(Extensible Authentication Protocol) 是一个在无线网络或点对点连线中普遍使用的认证框架.它被定义在RFC 3748中,并且使RFC ...

  2. 关于数论分块里r=sum/(sum/l)的证明!

    今天的模拟赛里T2要使用到数论分块,里面有一个重要的坎就是关于r=sum/(sum/l)的证明,网上关于这道题的题解里都没有关于这个的证明,那么我就来填补一下: 在以下的文章里,我都会使用lo(x)表 ...

  3. 『题解』洛谷P2357 守墓人

    Portal Portal1: Luogu Description 在一个荒凉的墓地上有一个令人尊敬的守墓人,他看守的墓地从来没有被盗过, 所以人们很放心的把自己的先人的墓安顿在他那守墓人能看好这片墓 ...

  4. 在linux上使用ssh登录服务器,Linux权限

    本文是作者原创,版权归作者所有.若要转载,请注明出处 ssh为Secure Shell(安全外壳协议)的缩写. 很多ftp.pop和telnet在本质上都是不安全的. 我们使用的Xshell6就是基于 ...

  5. 本地Git连接GitLab(服务器)远程仓库

    1.简介 远程仓库是指托管在网络上的项目仓库,现在互联网上有很多项目托管平台,比如github.gitlab等.为了不公开自己项目代码,可以在自己的服务器上搭建自己的项目仓库,最常见的是搭建GitLa ...

  6. Laravel上传文件(单文件,多文件)

    为了方便,先修改一个配置文件,再laravel框架中config配置中找到  filesystems.php 文件 修改代码如下 'local' => [ 'driver' => 'loc ...

  7. Thrift总结(四)Thrift实现双向通信

    前面介绍过 Thrift 安装和使用,介绍了Thrift服务的发布和客户端调用,可以查看我之前的文章:https://www.cnblogs.com/zhangweizhong/category/10 ...

  8. nyoj 70-阶乘因式分解(二)(数学)

    70-阶乘因式分解(二) 内存限制:64MB 时间限制:3000ms 特判: No 通过数:7 提交数:7 难度:3 题目描述: 给定两个数n,m,其中m是一个素数. 将n(0<=n<=2 ...

  9. nyoj 243-交换输出 (swap)

    243-交换输出 内存限制:64MB 时间限制:3000ms 特判: No 通过数:16 提交数:39 难度:1 题目描述: 输入n(n<100)个数,找出其中最小的数,将它与最前面的数交换后输 ...

  10. 领扣(LeetCode)最长公共前缀 个人题解

    编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow" ...