采用C语言编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定。典型的例子有大家熟悉的函数printf()、scanf()和系统调用execl()等。那么它们是怎样实现的呢?

C编译器通常提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括va_start、va_arg和va_end等。在讲解以上宏之前我们先了解一下调用函数时传入参数的处理过程。

一、函数传入参数过程

一个函数包括函数名、传入参数、返回参数以及函数体,函数体编译后的二进制代码储存在程序代码区。当用户调用某个函数时,系统会通过函数名(C++中会涉及到mangled命名处理)查找函数体入口指针并压入栈中,之后将传入参数以从右至左的顺序压入栈中(栈空间是往低地址方向增长的,也即栈底对应高地址,栈顶对应低地址),示例如下:

#include <iostream>
using namespace std; void fun(int a, ...)
{
int *temp = &a;
temp++;
for (int i = ; i < a; ++i)
{
cout << *temp << endl;
temp++;
}
} int main()
{
int a = ;
int b = ;
int c = ;
int d = ;
fun(, a, b, c, d); system("pause");
return ;
}
// Output:
// 1
// 2
// 3
//

二、va_start、va_arg和va_end宏定义

接着我们再来看看va_start、va_arg和va_end等宏的具体定义。

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是将类型n的大小向上取成4的倍数,如n为char型的话结果即为4

#ifdef  __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是将v的地址重新解释成char*型
#else
#define _ADDRESSOF(v) ( &(v) )
#endif #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 ) #ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char * va_list; // vs2015中此句高亮
#endif /* _M_CEE_PURE */
#define _VA_LIST_DEFINED
#endif
// 以上宏定义出现在vadef.h,通过stdio.h即可使用
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
// 以上宏定义出现在stdarg.h中,若要使用则需加上 #include <stdarg.h>

三、va_start、va_arg和va_end使用示例

容易看出,以上宏定义主要涉及地址操作,va_start获取第二个传入参数的地址给va_list类型变量(假设为arg_ptr),va_arg用于获取当前参数值并将指针arg_ptr往后移,va_end则是将arg_ptr置为空。va_start、va_arg、va_end具体用法示例如下:

#include <stdio.h>
#include <stdarg.h> int fun(int x, int y) {
return x - y;
} int fun(int count, ...) {
va_list arg_ptr; // 等同于 char *arg_ptr;
int nArgValue = count;
int nArgCout = ;
va_start(arg_ptr, count); // 使arg_ptr指向第二个参数的地址 printf("The 1 th arg: %d\n", nArgValue); // 输出第一个参数的值
int sum = ;
for (int i = ; i < count; i++)
{
++nArgCout;
nArgValue = va_arg(arg_ptr, int);// 将arg_ptr所指参数返回成int并移动arg_ptr使其指向后一个参数,这里假设传入参数均是int型
printf("The %d th arg: %d\n", i+, nArgValue); // 输出各参数的值
sum += nArgValue;
} va_end(arg_ptr); // 将arg_ptr置为空
return sum;
} int main() {
cout << fun() << endl;
cout << fun(, ) << endl; // 优先匹配到函数int fun(int, int), 输出0
cout << fun(, , ) << endl;
system("pause"); return ;
}
// Output:
// 0
// 0
// The 1 th arg: 2
// The 2 th arg: 3
// The 3 th arg: 4
//

注意:以上宏操作并不提供参数个数获取操作,这需要用户在函数中获取,如第二个fun函数使用count指明个数,printf通过解析第一个传入参数来确定参数个数与类型等。

参考资料:

百度百科-可变参函数

CSDN-va_start、va_arg、va_end用法

深度探索va_start、va_arg、va_end的更多相关文章

  1. va_list/va_start/va_arg/va_end深入分析【转】

    转自:http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html va_list/va_start/va_arg/va_end ...

  2. va_start,va_arg,va_end的使用

    一.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表. void fun(...); void fun(parm_list,...); #include <stdi ...

  3. C++省略参数(va_list va_start va_arg va_end)的简单应用

    原文参考自:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html #include <iostream> #in ...

  4. va_list/va_start/va_arg/va_end深入分析

    http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html

  5. va_start(),va_end()函数应用【转】

    转自:http://www.cnblogs.com/gogly/articles/2416833.html 原理解释: VA_LIST 是在C语言中解决变参问题的一组宏,在<stdarg.h&g ...

  6. va_start(),va_end()函数应用

    原理解释: VA_LIST 是在C语言中解决变参问题的一组宏,在<stdarg.h>头文件下. VA_LIST的用法:            (1)首先在函数里定义一具VA_LIST型的变 ...

  7. va_list、va_start和va_end使用

    我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1.硬件平台的不同 2.编译器的不同,所以定义的宏也有所不同. 在ANSI C中,这些宏的定义位于stdar ...

  8. va_start和va_end使用详解

    本文主要介绍va_start和va_end的使用及原理. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 ...

  9. [转载]va_start和va_end使用详解

    va_start和va_end使用详解 原文地址:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_s ...

随机推荐

  1. BZOJ1614: [Usaco2007 Jan]Telephone Lines架设电话线

    1614: [Usaco2007 Jan]Telephone Lines架设电话线 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 892  Solved: ...

  2. 【响应式Web设计实践 #BOOK#】

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  3. apache 服务发布多个项目,只需要更改配置文件(需要设定虚拟主机)

    http://www.php186.com/content/article/apache/24609.html http://blog.sina.com.cn/s/blog_6b689d5901013 ...

  4. 在Xcode中使用Clang Format

    Xcode中的Re-Indent,顾名思义,只是一个调整缩进的功能,完全依赖它来进行代码格式化显然不够用.我们使用了一个叫做ClangFormat-Xcode的插件,配合Re-Indent一起来做代码 ...

  5. 关于如何导入GPUImage

    今天是我第一天做项目哈,由于前几天实在是没有很多空,我姐结婚,事情太多,所以今天才开始真正写项目,希望不要太晚. 今天是晚上开始的,预期的计划就是能够把GPUImage成功导入,然后使用一些其中的东西 ...

  6. YUV像素和ycbcr

    一幅彩色图像的基本要素是什么? 说白了,一幅图像包括的基本东西就是二进制数据,其容量大小实质即为二进制数据的多少.一幅1920x1080像素的YUV422的图像,大小是1920X1080X2=4147 ...

  7. Erasing Edges - SGU 136(构造多边形)

    题目大意:已知一个多边形上的每条边的中点,还原出来一个多边形. 分析:因为偶数是不固定的,所以可以为任意起点,奇数只有一个,可以所有中点加减算出来第一个点,然后就是简单的向量计算点的位置了...... ...

  8. 基于TCP/IP的Matlab Modbus与M340 PLC通讯

    本人原创,代码拿出来供大家交流学习经验,勿作他用. 废话不多说,代码直接上. 1.创建链接 function link = connect_create(client_addr,port) %**** ...

  9. C# 操作配置文件

    1.配置文件app.config的书写: string mdbPath = ConfigurationManager.AppSettings["mdbPath"].ToString ...

  10. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(27)-权限管理系统-分配用户给角色

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(27)-权限管理系统-分配用户给角色 分配用户给角色,跟分配角色给用户操作是基本一致的. 打开模块维护,展 ...