va_start、va_arg、va_end、va_copy 可变参函数

1、应用与原理

在C语言中又是我们无法给出一个函数参数的列表,比如:


  int printf(const char *format, ...);

  int fprintf(FILE *stream, const char *format, ...);

这是我们使用到了可以变参数,也就是使用...代表0个或多个参数。

那么编译器如何获取/使用这些参数。这涉及到参数的传递原理:

参数传递原理:

在内存中,函数的参数以栈的方式存取,从右到左入栈。这些参数存放的地址是连续的。这样,我们就可以通过获取第一个参数的地址,以及各个参数的地址偏移量,就可以获取每个参数的地址,从而得到每一个参数。

2、va_start、va_arg、va_end、va_copy介绍


       #include <stdarg.h>



       void va_start(va_list ap, last);

       type va_arg(va_list ap, type);

       void va_end(va_list ap);

       void va_copy(va_list dest, va_list src);

va_list是一个指向参数首地址的指针,


typedef struct {

char *a0; /* pointer to first homed integer argument */

int offset; /* byte offset of next parameter */

} va_list;

va_start   

对ap进行了一系列的初始化,之后ap会被va_arg和va_end使用到。last是可变参数...的前一个参数,如fun(char
*fmt, ...)   中的fmt。通过va_start初始化ap,我们就获得了可变参数前一个参数fmt的地址。

va_arg   

va_arg用于获取可变参数...的每一个参数。如函数fun(char *fmt, ...)的一次调用fun(fmt, arg1, arg2, arg3)。在使用va_start()进行ap的初始化后,我们调用一次va_arg(ap,type)就获得了参数arg1,在调用一次就获得arg2,……从而,得到每一个参数的值。

va_end 

va_end用于清理ap的值,与va_start()配对使用。

va_copy

不常用,暂不介绍。

3、Example

先给一个man手册里的例子


//foo.c



#include <stdarg.h>

#include <stdio.h>



void

foo(char *fmt, ...)

{

   va_list ap;

   int d;

   char c, *s;



   va_start(ap, fmt);

   while (*fmt)

       switch (*fmt++) {

       case ‘s‘:              /* string */

           s = va_arg(ap, char *);

           printf("string %s\n", s);

           break;

       case ‘d‘:              /* int */

           d = va_arg(ap, int);

           printf("int %d\n", d);

           break;

       case ‘c‘:              /* char */

           /* need a cast here since va_arg only

           /* need a cast here since va_arg only

              takes fully promoted types */

           c = (char) va_arg(ap, int);

           printf("char %c\n", c);

           break;

       }

   va_end(ap);

}

//main.c

#include <stdio.h>

#include <stdarg.h>

#include "foo.h"



int

main(void)

{

        foo("%s %d %c %d %c", "Hello", 4, ‘x‘, 3, ‘y‘);

        return 0;

}

执行结果:


[email protected]:~/Windeal/apue$ ./exe 

string Hello

int 4

char x

int 3

char y

[email protected]:~/Windeal/apue$

使用vsprintf的例子


//foo.c

#include <stdarg.h>

#include <stdio.h>



void

foo(char *fmt, ...)

{

        va_list ap;

        int len = 0;

        char buf[64];



        va_start(ap, fmt);

        len = vsnprintf(buf, 128, fmt, ap);

        va_end(ap);



        int i = 0;

        for(i = 0; i < len; i++)

        {

                putchar(buf[i]);

        }

        return ;

}

~

// main.c

#include <stdio.h>

#include <stdarg.h>

#include "foo.h"



int

main(void)

{

        foo("Test:%s %d %c %d %c\n", "Hello", 4, ‘x‘, 3, ‘y‘);

        return 0;

}

执行结果:


[email protected]:~/Windeal/apue$ ./exe 

Test:Hello 4 x 3 y

时间: 2024-08-04 23:10:14

va_start、va_arg、va_end、va_copy 可变参函数的相关文章

va_list &amp; va_start &amp; va_arg &amp; va_end

va_list 属于变量 而 va_start & va_arg & va_end  C语言中解决变参问题的一组宏.头文件来自stdarg.h. 查看linux系统源码方式我一般用locate stdarg.h,然后找到提示目录vi进去.源文件定义是: 在Mac 下追踪头文件也会发现如下宏定义: typedef __darwin_va_list va_list; typedef __builtin_va_list    __darwin_va_list;    /* va_list */

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\n",*temp); temp++; } } int main() { int a=1,b=2,c=3,

va_list/va_start/va_arg/va_end深入分析

va_list/va_start/va_arg/va_end深入分析 va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的. 我们来看看在vs2008中,它们是怎么定义的: 1: ///stdarg.h 2: #define va_start _crt_va_start 3: #define va_arg _crt_va_arg 4: #define va_end _crt_va_end 5: 6: ///vadefs.h 7: #define _ADD

【转】C/C++中可变参数的详细介绍(va_list,va_start,va_arg,va_end)

可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现. 由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,提出了指针参数来解决问题. 如printf()函数,其原型为:int   printf(   const  

va_start、va_end、va_arg 实现可变长参数

/*******************************///va_start.va_end.va_arg 实现可变长参数/*******************************/ #include <stdarg.h>#include <stdio.h> #define END_NUM -1void trease_array(int first_num,...){ int result1 = 0; int result2 = 0; int temp = 0; va

va_start和va_end使用详解 转

介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它. 2.函数参数的传递原理 函数参数是以数据结构:栈的形式存取,从右至左入栈. 首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈.因此栈底高地址,栈

va_start和va_end的使用及原理【转】

本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用

va_start和va_end使用详解

va_start和va_end使用详解 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我们要记住

va_start和va_end使用详解(转载)

转自:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);

[转载]va_start和va_end使用详解

va_start和va_end使用详解 原文地址:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end这两个宏,但对它们也只是泛泛的了解. 介绍这两个宏之前先看一下C中传递函数的参数时的用法和原理: 1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省