采用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 = 0; i < a; ++i) { cout<< *temp << endl;
      temp++;
    }
}

int main()
{
   int a = 1;
   int b = 2;
   int c = 3;
   int d = 4;
    fun(4, a, b, c, d);

system("pause");
   return 0;
}
// Output:
// 1
// 2
// 3
// 4

二、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
typedefchar *  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 = 0; 
    va_start(arg_ptr, count); // 使arg_ptr指向第二个参数的地址

printf("The 1 th arg: %d\n", nArgValue);    // 输出第一个参数的值
    int sum = 0;
   for (int i = 0; i < count; i++)
    {
       ++nArgCout;
        nArgValue= va_arg(arg_ptr, int);// 将arg_ptr所指参数返回成int并移动arg_ptr使其指向后一个参数,这里假设传入参数均是int型
        printf("The %d th arg: %d\n", i+2, nArgValue);  // 输出各参数的值
        sum += nArgValue;
    }

va_end(arg_ptr);   // 将arg_ptr置为空
    return sum;
}

int main() {
    cout<< fun(0) << endl;
    cout<< fun(1, 1) << endl;  // 优先匹配到函数int fun(int, int), 输出0
    cout << fun(2, 3, 4) << endl;
   system("pause");

return 0;

}

// Output:
// 0
// 0
// The 1 th arg: 2
// The 2 th arg: 3
// The 3 th arg: 4
// 7

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

深度探索va_start、va_arg、va_end相关推荐

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

    va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的. 我们来看看在vs2008中,它们是怎么定义的: 1: ///stdarg.h 2: #define v ...

  2. 变长参数va_list va_start va_arg va_end

    对于int printf(const char *format, ...);这种变长参数,需要使用va_list va_start va_end va_arg来访问参数. 下面是一个tutorials ...

  3. C语言使用函数参数传递中的省略号:va_list, va_start, va_arg, va_end

    首先要处理这种省略号的参数的话,需要包含头文件#include <stdarg.h>,然后利用下面的函数对"..."省略号变量进行处理. va_list arg; ty ...

  4. 如何获取函数的变长参数(va_list, va_start, va_arg, va_end)

    最近在花时间研读C++. 函数这章讲到了函数的变长参数(ellipsis...),但是primer中讲得比较浅,提到了怎么声明怎么调用,但是没有写明在函数内部是如何获取变长的参数的. 1)省略号(el ...

  5. 可变参数的函数,va_start(), va_arg(), va_end()

    头文件 stdarg:类型va_list:宏va_start:宏va_arg:宏va_end 1 #include <stdio.h>2 #include <stdarg.h> ...

  6. Va_list Va_start va_arg Va_end 的用法

    首先我们先看看它的头文件是怎么描述的 stdarg.h #pragma once#ifndef _INC_STDARG #define _INC_STDARG#if !defined(_WIN32) ...

  7. 可变参数:va_list(),va_start(),va_arg(),va_end() 详细解析

    目录 1.含义: 2.使用: 3.连续打印出自定义格式的文字: 1.含义: (1)va_list是C语言中的一个宏定义,用于表示一个变长参数列表.它是一个指向变长参数列表的指针,可以通过宏va_sta ...

  8. stdarg.h中三个宏va_start ,va_arg\va_end及vsprintf 的应用

    我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() 这个函数,它的定义是这样的: int printf( const char* format, ...); 它除了有一个参数form ...

  9. va_list、va_start和va_end使用

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

最新文章

  1. 使用alipaySDK编译时找不到openssl/asn1.h文件的解决办法(初探)
  2. R语言quantmod包
  3. DotNetty 实现 Modbus TCP 系列 (三) Codecs Handler
  4. java开源服务框架_Java框架服务
  5. 《C和指针》——字符数组和字符串常量的区别
  6. Hadoop系列之FieldSelectionMapReduce用法
  7. Alexa 世界网站排名研究
  8. 《软件工程(第4版?修订版)》—第2章2.9节本章对研究人员的意义
  9. python能做什么项目-Python 的练手项目有哪些值得推荐?
  10. Java Web中界面之间传值的实现方法
  11. qi无线充电协议_iOS 13.1封杀第三方无线快充:疑似苹果无线充电私有协议来了...
  12. nodejs+vue 智能餐厅菜品厨位分配管理系统
  13. 无源码程序反编译修改文字
  14. 课程设计实验--火车票座位分配
  15. 树状数组入门——以洛谷3374为例
  16. mysqlit根据稀有值随机选择_三中锋教练来过了!实况足球20赛季DP7.0后新增稀有阵型top10...
  17. 上海青浦区大众驾校(科目二·自动挡)真实考场操作全程
  18. 系统调用中断(EINTR)与SIGCHLD信号的处理
  19. 【回首2022,展望2023,兔年你好!】
  20. 添加集控程序的守护进程一般操作【Linux,CentOS7.5】

热门文章

  1. why do you need that a awesome linkedin profile
  2. a good resource gathering system from sustech
  3. where is lingang city in shanghai?
  4. iPad导入Mac:非常快!一气呵成,直接去photo里面选择,之后左上角倒出就好,颠覆之前windows上面的认知!
  5. 学到了关于服务器磁盘阵列
  6. 【转】6 个技巧,提升 C++11 的 vector 性能
  7. 在eclipse及myEclipse下安装插件之方法
  8. 欧拉函数的相关应用 noj欧拉函数求和+noj 最大公约数求和
  9. 你真的会使用assert吗?
  10. 【Android】Android 8种对话框(Dialog)