加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 黄冈站长网 (http://www.0713zz.com/)- 数据应用、建站、人体识别、智能机器人、语音技术!
当前位置: 首页 > 教程 > 正文

深度研究va_start、va_arg、va_end

发布时间:2021-12-10 17:34:32 所属栏目:教程 来源:互联网
导读:采用C语言编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定。典型的例子有大家熟悉的函数printf()、scanf()和系统调用execl()等。那么它们是怎样实现的呢

   采用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: %dn", 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: %dn", 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通过解析第一个传入参数来确定参数个数与类型等。

(编辑:PHP编程网 - 黄冈站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读