• va_start,va_arg,va_end的使用


    一、在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表。
    void fun(...);
    void fun(parm_list,...);
    #include <stdio.h>
    void fun(int n,...)
    {
        int *temp=&n;
        temp++;
        for(int i=0;i<n;i++)
        {
            printf("%d
    ",*temp);
            temp++;
        }
    }
    int main()
    {
        int a=1,b=2,c=3,d=4;
        fun(4,a,b,c,d);
        return 0;
    }

    二、使用va_start,va_arg,va_end

    #include <stdio.h>
    #include <stdarg.h>
    void fun(int n,...)    //变参函数,C++里也有
    {
        int temp,i;
        va_list ap;     //step1:va_list类型数据可以保存函数的所有参数,做为一个列表一样保存
     
        va_start(ap,n);   //step2:对ap 进行初始化,让ap指向可变参数表里面的第一个参数(最后一个固定参数后的可变参数)
        for(i=0;i<n;i++)
        {
            temp=va_arg(ap,int);  //step3:把 ap 的位置指向变参表的下一个变量位置
            printf("%d
    ",temp);
        }
        va_end(ap);   //step4:关闭ap指针
    }
    int main()
    {
        int a=1,b=2,c=3,d=4;
        fun(4,a,b,c,d);
        return 0;
    }

    <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
     
    <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
     
    <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
     
    <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
     
    三、函数参数传递原理
    这里要知道两个事情:
         ⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
         (2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。  
      栈底 高地址
        | .......     
        | 函数返回地址
        | .......      
        | 函数最后一个参数
        | ....                       
        | 函数第一个可变参数       <--va_start后ap指向  
        | 函数最后一个固定参数
        | 函数第一个固定参数  
        栈顶 低地址
    所使用到的宏:
         void va_start( va_list arg_ptr, prev_param );
         type va_arg( va_list arg_ptr, type );
         void va_end( va_list arg_ptr );
    宏解释:
        typedef char *   va_list;
         #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
         #define va_start(ap,v)   ( ap = (va_list)&v + _INTSIZEOF(v) )
         #define va_arg(ap,t)     ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
         #define va_end(ap)       ( ap = (va_list)0 )
    1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。
     
    4、va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
    因此,现在再来看va_arg()的实现就应该心中有数了:
         #define va_arg(ap,t)     ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    这个宏做了两个事情,
        ①ap指向下一个参数:ap += _INTSIZEOF(t)
        ②表达式取得本参数的值:先是让ap指向下一个参数:ap += _INTSIZEOF(t),然后在减去_INTSIZEOF(t),使得表达式结果为ap之前的值,即当前需要得到的参数的地址,强制转换成指向此参数的
    类型的指针,然后用*取值
     
    int main(void)
    {
        int a=0,b=0;
        b=((a+=1)-1);
        printf("a:%d,b:%d
    ",a,b);
        b=((a+=1)-1);
        printf("a:%d,b:%d",a,b);
        return 0;
    }

    5、va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
     
  • 相关阅读:
    百度百科目录导航树小插件
    Docker for windows部署mysql挂载数据卷
    ASP.NET CORE 2.0 不小心踩得坑
    获取MVC中Controller下的Action参数异常
    DataTableToList
    svn禁止提交的文件
    plush
    解决端口号被占用的问题
    vue-router
    vue-layer
  • 原文地址:https://www.cnblogs.com/elie/p/3933431.html
Copyright © 2020-2023  润新知