注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

时间记录器

记录我的Linux、Android学习之路

 
 
 

日志

 
 

从printf谈可变参数函数的实现 (ZT)  

2010-08-19 17:00:29|  分类: C语言相关 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

转贴
原文作者:戎亚新
摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒。printf这种对参数个数和参数类型的强大适应性,让人产生了对它进行探索的浓厚兴趣。
关键字:printf, 可变参数
1. 使用情形
int a =10;
double b = 20.0;
char *str = "Hello world";
printf("begin print\n");
printf("a=%d, b=%.3f, str=%s\n", a, b, str);
...
 
 从printf的使用情况来看,我们不难发现一个规律,就是无论其可变的参数有多少个,printf的第一个参数总是一个字符串。而正是这第一个参数,
使得它可以确认后面还有有多少个参数尾随。而尾随的每个参数占用的栈空间大小又是通过第一个格式字符串确定的。然而printf到底是怎样取第一个参数后
面的参数值的呢,请看如下代码
2. printf 函数的实现
//acenv.h
typedef char *va_list;
#define  _AUPBND        (sizeof (acpi_native_int) - 1)
#define  _ADNBND        (sizeof (acpi_native_int) - 1)
                        
#define _bnd(X, bnd)    (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)   (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)      (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
//start.c
static char sprint_buf[1024];
int printf(char *fmt, ...)
{
        va_list args;
        int n;
        va_start(args, fmt);
        n = vsprintf(sprint_buf, fmt, args);
        va_end(args);
        write(stdout, sprint_buf, n);
        return n;
}
//unistd.h
static inline long write(int fd, const char *buf, off_t count)
{
        return sys_write(fd, buf, count);
}
3. 分析
 
 从上面的代码来看,printf似乎并不复杂,它通过一个宏va_start把所有的可变参数放到了由args指向的一块内存中,然后再调用
vsprintf.
真正的参数个数以及格式的确定是在vsprintf搞定的了。由于vsprintf的代码比较复杂,也不是我们这里要讨论的重点,所以下面就不再列出了。
我们这里要讨论的重点是va_start(ap, A)宏的实现,它对定位从参数A后面的参数有重大的制导意义。现在把 #define
va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd
(A,_AUPBND)))) 的含义解释一下如下:
    va_start(ap, A)
    {
         char *ap =  ((char *)(&A)) + sizeof(A)并int类型大小地址对齐
    }
 
 在printf的va_start(args, fmt)中,fmt的类型为char *, 因此对于一个32为系统 sizeof(char *)
= 4, 如果int大小也是32,则va_start(args, fmt);相当于 char *args = (char
*)(&fmt) + 4; 此时args的值正好为fmt后第一个参数的地址。对于如下的可变参数函数
    void fun(double d,...)
    {
        va_list args;
            int n;
            va_start(args, d);
    }
则 va_start(args, d);相当于
    char *args = (char *)&d + sizeof(double);
  此时args正好指向d后面的第一个参数。
  可变参数函数的实现与函数调用的栈结构有关,正常情况下c/c++的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。对于函数
    void fun(int a, int b, int c)
    {
        int d;
        ...
    }
其栈结构为
    0x1ffc-->d
    0x2000-->a
    0x2004-->b
    0x2008-->c
  对于任何编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单元大小,如函数 void fun1(char a, int b, double c, short d) 对一个32的系统其栈的结构就是
    0x1ffc-->a  (4字节)
    0x2000-->b  (4字节)
    0x2004-->c  (8字节)
    0x200c-->d  (4字节)
  对于函数void fun1(char a, int b, double c, short d)
 
 如果知道了参数a的地址,则要取后续参数的值则可以通过a的地址计算a后面参数的地址,然后取对应的值,而后面参数的个数可以直接由变量a指定,当然也
可以像printf一样根据第一个参数中的%模式个数来决定后续参数的个数和类型。如果参数的个数由第一个参数a直接决定,则后续参数的类型如果没有变化
并且是已知的,则我们可以这样来取后续参数, 假定后续参数的类型都是double;
void fun1(int num, ...)
{
    double *p = (double *)((&num)+1);
    double Param1 = *p;
    double Param2 = *(p+1);
    ...
    double Paramn  *(p+num);
}
 
 如果后续参数的类型是变化而且是未知的,则必须通过一个参数中设定模式来匹配后续参数的个数和类型,就像printf一样,当然我们可以定义自己的模
式,如可以用i表示int参数,d表示double参数,为了简单,我们用一个字符表示一个参数,并由该字符的名称决定参数的类型而字符的出现的顺序也表
示后续参数的顺序。 我们可以这样定义字符和参数类型的映射表,
i---int
s---signed short
l---long
c---char
"ild"模式用于表示后续有三个参数,按顺序分别为int, long, double类型的三个参数那么这样我们可以定义自己版本的printf 如下
void printf(char *fmt, ...)
{
    char s[80] = "";
    int paramCount = strlen(fmt);
    write(stdout, "paramCount = " , strlen(paramCount = ));
    itoa(paramCount,s,10);
    write(stdout, s, strlen(s));
    char *p = (char *)(&fmt) + sizeof(char *);
    int *pi = (int *)p;
    for (int i=0; i
    {
        char line[80] = "";
        strcpy(line, "param");
        itoa(i+1, s, 10);
        strcat(line, s);
        strcat(line, "=");
        switch(fmt = (char)(*pi);
                    line[len+1] = '\0';
                }
                break;
            case 'l':
                ltoa((*(long *)pi),s,10);
                strcat(line, s);
                pi++;
                break;
            default:
                break;
        }
    }
}
也可以这样定义我们的Max函数,它返回多个输入整型参数的最大值
int Max(int n, ...)
{
    int *p = &n + 1;
    int ret = *p;
    for (int i=0; i
    {
        if (ret

 

好文片段:

 

最常用的printf()函数的实现:
int printf(const char *fmt, ...)
printf()函数的参数个数并不确定,如何获得下一个参数的地址?这是一个最基本而看似简单的问题,你是否联想到把fmt的地址加1就是下一个参数的地址。而这背后的基础就是对参数存放位置的认知。编译程序把参数放在何处,是如何存放的?这些问题是语言之外的知识。当你有了这些基础之后,你自然会写出如下的关键语句:
  int *para = (int *)&fmt+ 1;
 有了这样一个语句以后,你就可以把变参函数回退到确定参数的函数,比如:
 vprintf(char *buf,const char *fmt, int *para);

  评论这张
 
阅读(266)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017