Chinaunix首页 | 论坛 | 博客
  • 博客访问: 259490
  • 博文数量: 74
  • 博客积分: 1470
  • 博客等级: 上尉
  • 技术积分: 793
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-25 21:01
文章分类

全部博文(74)

文章存档

2011年(1)

2010年(32)

2009年(32)

2008年(9)

我的朋友

分类: LINUX

2009-12-13 16:48:15

你是否碰到过形如
void log(char *fmt, ...)
{
 ..........
}
的函数呢?
如果没有的话可以man 3 printf

#include <stdio.h>
       int printf(const char *format, ...);

不错,大家最常见的printf就是这样的一个例子,当无法列出传递给函数的所有实参的类型和数目时,可用省略号指定参数表
 
那么这个...到底是怎么实现的呢?
 
我们先看这几个宏:
#include
 void va_start(va_list ap, last);
 type va_arg(va_list ap, type);
 void va_end(va_list ap);
这几个宏都要用到一个va_list类型的变量,这个变量的定义也在
 
------------------va_start()-------------------
这个宏的原型如下:
void va_start(va_list ap, last);
这个宏主要的作用就在于定义一个va_list类型的变量,供后边的va_arg和va_end使用,所以这个宏得最先被调用.
这里的参数last指的是最后一个有确定类型的变量的名字,通常指的就是...之前的那个变量的名字,而ap则是...所代表的参数集.
如void log(char *fmt, a,b,c),此时last就应该是fmt,而宏调用完后ap就应该是a,b,c了
因为va_start宏可能会用到last的地址,所以last不应该是寄存器变量,函数或数组
 
-------------------va_arg()-------------------
这个宏的原型如下:
type va_arg(va_list ap, type);
这里的参数ap就是va_start定义的那个ap,每调用一次va_arg,就会修改ap的值,使其向前移动一个参数,这样的话,下一次调用va_arg就能返回下一个值
参数type指的是你希望ap中的当前变量以什么类型来获得.
沿用上边va_start的例子:va_arg(ap,char*);此时ap是a,b,c 而你希望以字符串的形式读入a,所以type写成char*,当这个宏调用完毕后,va_arg就是b,c了,而下一次调用va_arg返回的就是b,如果你希望以int来读取b,那就应该va_arg(ap,int),再下一次就是c
如果ap中没有下一个参数或者下一个参数的类型和你希望的类型type不一致,那就会出现随机的error
 
-------------------va_end()-------------------
这个宏的原型如下:
void va_end(va_list ap);
在大多数C语言上,调用va_end与否并无区别。但是,某些版本的va_start宏为了方便对va_list进行遍历,就给参数列表动态分配内存,这样当调用va_end以后ap这个变量就会释放,所以如果忘记调用va_end,可能会引起内存泄露。
下面写一个简单的例子,来实现简单的printf函数:
 

#include <stdio.h>
#include <stdarg.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("%s\n", s);
                break;
            case 'd': /* int */
                d = va_arg(ap, int);
                printf("%d\n", d);
                break;
            case 'c': /* char */
                /* need a cast here since va_arg only
                 takes fully promoted types */

                c = (char) va_arg(ap, int);
                printf("%c\n", c);
                break;
        }
    va_end(ap);
}

int main()
{
    foo("%s%d%c","inet",11,'a');
    return 0;
}

其实ANSI C标准要求,并且很多C语言实现也提供的vprintf,vfprintf,vsprintf系列函数就是来实现这一功能的,这些函数与对应的printf函数族中的函数在行为方式上完全一样,只不过用va_list代替了可变参数列表。所以实现printf函数可以这样实现:

#include <stdio.h>
#include <stdarg.h>

void
foo(char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);

    vprintf(fmt,ap);
    va_end(ap);
}

int main()
{
    foo("%s%d%c","inet",11,'a');
    return 0;
}

 

变长参数列表主要用于配置文件、选项、参数的格式化分析,也多用于项目中的log
或错误处理,当项目中随着程序规模的增大,程序员经常感觉到有必要进行系统化的错误处理,所以就很自然的可以利用这个方法来实现如下形式的错误处理:

erro_log("%s is illagle",str);

reference:
man va_start
《c陷阱与缺陷》
阅读(858) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~