帮助理解可变参数的应用【转贴】

来源:互联网 发布:mac有必要flash吗 编辑:程序博客网 时间:2024/05/01 16:04

C语言中不用宏va_list,va_start,va_arg写处理变长参数的函数


http://blog.sina.com.cn 2006年09月07日10:54 随机过程 标签:
 
C语言中变长参数(va_list,va_start,va_arg)沉思录
 
一.引言:
 
C语言中关于变长参数的使用很简单,无非是如下的框架。是否可以不用宏而编写处理变长参数的函数呢?答案是肯定的,本文作了一些处浅探讨,不足之处望各位批评指正。
 
使用宏的程序框架:
#include <stdio.h>
#include <stdart.h>  /* 或者#include <vararg.h> */
 
int print (char * fmt, ...)
{
    va_list args;
    ...
 
    va_start (args, fmt);
  
    /* do something here */
 
    va_end (args);
 
    /* do something here too */
}
 
我看了一下有关va_list, va_start, va_end宏的定义,各编译器不大一样,我着重研究了一下gcc的以上三个宏,并在不用三个宏的情况下编写了测试程序。测试过程和大家分享。
 
gcc中va_list的定义
#define char*  va_list   /* gcc中va_list等同char* */
 
gcc中三个宏的定义如下(经过我加工整理后):
#define va_start(AP, LASTARG) ( /
            AP = ((char *)& (LASTARG) + /
            __va_rounded_size(LASTARG)))
 
#define va_arg(AP, TYPE) ( /
            AP += __va_rounded_size(TYPE), /
            *((TYPE *)(AP - __va_rounded_size(TYPE))))
 
#define va_end(AP)              /* 没有定义,没有操作 */
有的编译器这样定义:
#define va_end(AP) ((void *)0)  /* 有定义,是空操作 */
 
二.不用宏的处理变长参数实践:
 
本人在分析了上面的宏后,在Linux、 i386、 gcc、 gas平台的Think Pad R50e上不用上面的三个宏成功写了个测试C代码,并详细分析了该代码的汇编代码,先给出代码如下:
 
/*****************************************************
 * File Name : mytest.c          
 * Copyright by : Superware   
 * Version : 0.01             
 *****************************************************/
 
#include <stdio.h>            /* 我没包含 stdarg.h 或 vararg.h */
 
void print (char * fmt, ...)
{
    char * arg;               /* 变长参数的指针
                                 相当于 va_list arg */
    int i;                    /* 接受int参数 */
    double d;                 /* 接受double参数 */
    char c;                   /* 接受char 参数*/
    char *s;                  /* 接受字符串 */
 
    printf ("%s", fmt);       /* 打印第一个参数 fmt串 */
 
    arg = (char *)&fmt + 4;   /* 相当于 va_start(arg, fmt)
                                 这里的 +4 实际上是
                                 sizeof(char *) 因为在IA32
                                 中,所以我写了4 没有考虑移植,
                                 为什么? 在下面解释,
                                 注意这里加 4表示arg已经指向
                                 第二个参数 */
    /* 打印第二个参数 */
    i = *(int *)arg;          /* 接受第二个参数,
                                 为了直接了当,我硬性规定 
                                 print()函数的第二个
                                 参数是整数,请看 */
                                 main()函数中的print()
                                 函数调用 */
    printf ("%d", i);         /* 打印地二个参数,是整数,
                                 所以用格式"%d" */
    arg += sizeof(int);       /* 指向下一个参数(第三个参数),
                                 为什么是加sizeof(int),
                                 分析汇编码你就明白了 */
    /* 打印第三个参数 */
    d = *(double *)arg;       /* 以下的解释同地二个参数类似,
                                 就不详细解释了 */
    printf ("%f", d);
    arg += sizeof(double);
 
    /* 打印第四个参数 */
    c = *(char *)arg;
    printf ("%c", c);
    arg += sizeof (char *);
 
    /* 打印第五个参数 */
    c = *(char *)arg;
    printf ("%c", c);
    arg += sizeof (char *);
 
    /* 打印第六个参数 */
    s = *(char **)arg;
    printf ("%s", s);
    arg += sizeof (char *);
 
    arg = (void *)0;           /* 使arg指针为 (void)0,
                                  实际上就上使无效,否则arg
                                  依然指向第六个参数,危险。*/
                               /* 相当于 va_end(arg) */

}
 
int main (void)
{
    print ("Hello/n", 3, 3.6, '/n', 'a', "World/n");
    return 0;
}
/* File mytest.c ends here *******************/
 
 
代码有点长,其实很简单,只是机械地对main()函数中的print()函数中的6个参数进行处理,依次打印上面的6个参数(注意,我没有用格式化符号,带格式话符号的处理函数我将在下面给出),去掉注释,在Linux AS4.2 gcc-3.4.4中编译,运行结果如下:
 
/************************
Hello
33.600000
aWorld
*************************/
 
与预想的完全一致。说明print()函数对变长参数的理解是正确的。
 
原创粉丝点击