变长参数表

函数printf的正确声明形式为:

int printf(char *fmt, ...);

其中,省略号表示参数表中参数的数量和类型是可变的(省略号只能出现在参数表的尾部)。类似的参数表被称为边长参数表。它除了有一个参数fmt固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符)。

在《C程序设计语言》中,Ritchie提供了一个简易版printf函数minprintf:

#include <stdarg.h>

void minprintf(char *fmt, ...)
{
    va_list ap;    /* 依次指向每个无名参数 */
    char *p, *sval;
    int ival;
    double dval;

    va_start(ap, fmt);    /* 将ap指向第一个无名参数 */
    for (p = fmt; *p; p++) {
        if(*p != ‘%‘) {
            putchar(*p);
            continue;
        }
        switch(*++p) {
        case ‘d‘:
            ival = va_arg(ap, int);
            printf("%d", ival);
            break;
        case ‘f‘:
            dval = va_arg(ap, double);
            printf("%f", dval);
            break;
        case ‘s‘:
            for (sval = va_arg(ap, char *); *sval; sval++)
                putchar(*sval);
            break;
        default:
            putchar(*p);
            break;
        }
    }
    va_end(ap);    /* 结束时的清理工作 */
}

以上代码很简单,编写函数minprintf的关键在于如何处理一个甚至连名字都没有的参数表。下面我们从标准头文件<stdarg.h>说起。

<stdarg.h>中包含一组宏定义,它们对如何遍历参数表进行了定义。该头文件的实现因不同的机器而不同,但提供的接口是一致的。

typedef char *   va_list;     /* 其中va表示variable argument可变参数*/
#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_list是被定义成void*的;

2、_INTSIZEOF(n)咋一看有点令人费解,其实它主要是为了内存对齐,其原理可参考《_INTSIZEOF(n)解析》

3、va_start(ap, v),其参数ap为va_list类型,v为确定的参数fmt。其作用是初始化可变参数列表(把函数在fmt之后的参数地址放到ap中);

4、va_arg(ap, t)(t表示用户输入的类型type),( *(t *)( (ap += _INTSIZEOF(t)) - _INTSIZEOF(t) ) )这个式子不仔细看也会让人不解,ap怎么先加上_INTSIZEOF(t)有减去它,这不多此一举吗?其实不然,注意括号,ap+=自身变了,接着ap只是参与这个表达式计算而已,ap不会再变了。因此这个宏做了两件事:

(1)用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值;

(2)计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。

5、va_end(ap),x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的。

在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的。

参考文献:

1、http://blog.chinaunix.net/uid-2413049-id-109789.html

2、《The C Programming Language》

时间: 2024-10-09 23:46:46

变长参数表的相关文章

c++11 : Variadic Macros(变长参宏)

Variadic macros are function-like macros that contain a variable number of arguments. Remarks To use variadic macros, the ellipsis may be specified as the final formal argument in a macro definition, and the replacement identifier __VA_ARGS__ may be

Java语法糖初探(三)--变长参数

变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T -args).但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组. 看下面的例子: 12345678910111213 public class VariVargs { public static void main(String []args) { tes

C++11 新特性之 变长参数模板

template <typename ... ARGS> void fun(ARGS ... args) 首先明确几个概念 1,模板参数包(template parameter pack):它指模板参数位置上的变长参数,例如上面例子中的ARGS 2,函数参数包(function parameter pack):它指函数参数位置上的变长参数,例如上面例子中的args 一般情况下 参数包必须在最后面,例如: template <typename T, typename ... Args>

java 变长參数使用原则

1.java变长參数用...表示,如Print(String... args){  ... }; 2.假设一个调用既匹配一个固定參数方法.又匹配一个变长參数方法,则优先匹配固定參数的方法 3.假设一个调用能匹配两个及以上的变长參数方法,则出现错误--这事实上表示方法设计有问题,编译器会提示The method is ambiguous 4.方法仅仅能有一个变长參数,且必须放在參数列表的最后一个

变长结构体的使用

在分析安卓源码过程中看到几处使用变长结构体的例子,比如下面的结构体: struct command { /* list of commands in an action */ struct listnode clist; int (*func)(int nargs, char **args); int nargs; char *args[1]; }; 下面介绍安卓是如何使用这个结构的,在解析init.rc文件的过程中,会使用这个结构体记录某些命令. static void parse_line_

变长数组_相乘取结果

//变长数组 相乘取结果 #include <stdio.h> int main(void){ // int array_01[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; int array_02[4][3] = {12,11,10,9,8,7,6,5,4,3,2,1}; int result[3][3] = {0}; int i, j, k; for (i = 0; i < 3; i ++){ //遍历array_01数组元素 for (j = 0;j

scala学习笔记-变长参数(5)

变长参数 在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数. 1 def sum(nums: Int*) = { 2 var res = 0 3 for (num <- nums) res += num 4 res 5 } 6 7 sum(1, 2, 3, 4, 5) 使用序列调用变长参数 在如果想要将一个已有的序列直接调用变长参数函数,是不对的.比如val s = sum(1 to 5).此时需要使用Scala特殊的语法将参数定义为序列,让Scala解

读书笔记:c语言标准库 - 变长参数

· 变长参数(stdarg.h) 变长参数是c语言的特殊参数形式,例如如下函数声明: int printf(const char * format,...); 如此的声明表明,printf函数除了第一个参数类型为const char*之外,其后可以追加任意数量.任意类型的参数. 在函数实现部分,可以使用stdarg.h里的多个宏来访问各个额外的参数:假设lastarg是变长参数函数的最后一个具名参数(printf里的format),那么在函数内容定义类型为va_list的变量: va_list

C之变长数组

变长数组是C99标准新加入的一个特性,它的加入大大方便了我们的编程,所谓变长数组,不是数组的长度可变,而是指允许使用变量来定义数组.这可以使我们写出更具通用性的函数.下面是一个例子,函数sum2d完成将一个二位数组中的所有数值相加并返回其和. #include<stdio.h> #define SIZE 10 #define LOC 2 #define ROW 4int sum2d(int loc, int row, int num[loc][row]); int main(void){ in