vsprintf解析

printf函数:

栈是从内存的高地址向低地址生长的,函数参数压栈顺序是从右到左,printf的第一个参数就是那个字符指针即为被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数(%5.4lf等等)来判断参数个数及数据类型。例如printf("%d,%d",a,b);汇编代码为:

.section
.data
string out = "%d,%d"
push b
push a
push $out
call printf

参数是最后的先压入栈中,最先的后压入栈中,参数控制的那个字符串常量是最后被压入的 

几个宏:

C中变长实参头文件stdarg.h提供了一个数据类型va_list和三个宏(va_startva_argva_end),用它们在被调用函数不知道参数个数和类型时对可变参数表进行测试,从而为访问可变参数提供了方便且有效的方法。va_list是一个char类型的指针,当被调用函数使用一个可变参数时,它声明一个类型为va_list的变量,该变量用来指向va_arg和va_end所需信息的位置。下面给出va_list在C中的源码:

typedef char *  va_list; 

void va_start(va-list ap,lastfix)是一个宏,它使va_list类型变量ap指向被传递给函数的可变参数表中的第一个参数,在第一次调用va_arg和va_end之前,必须首先调用该宏。va-start的第二个参数lastfix是传递给被调用函数的最后一个固定参数的标识符

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )   //得到可变参数中第一个参数的首地址  

type va_arg(va_list ap,type)也是一个宏,其有双重目的,第一个是返回ap所指对象的值,第二个是修改参数指针ap使其增加以指向表中下一个参数。va_arg的第二个参数提供了修改参数指针所必需的信息。在第一次使用va_arg时,它返回可变参数表中的第一个参数,后续的调用都返回表中的下一个参数,下面给出va_arg在C中的源码:

#define va_arg(ap,type)    ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )    //将参数转换成需要的类型,并使ap指向下一个参数  

注意:type如果依次为依次为char型、char * 型、int型和float型时,在va-arg中它们的类型则应分别为int、char *、int和double.(对齐原则)

void va-end(va-list ap)也是一个宏,该宏用于被调用函数完成正常返回,功能就是把指针ap赋值为0,使它不指向内存的变量。下面给出va_end在C中的源码:

#define va_end(ap)      ( ap = (va_list)0 )  

va-end必须在va-arg读完所有参数后再调用,否则会产生意想不到的后果。

例子:

#include<iostream>
using namespace std;
#include<stdarg.h>  

int sum(int n,...)
{
    int i , sum = 0;
    va_list vap;
    va_start(vap , n);            //指向可变参数表中的第一个参数
    for(i = 0 ; i < n ; ++i)
        sum += va_arg(vap , int);     //取出可变参数表中的参数,并修改参数指针vap使其增加以指向表中下一个参数
    va_end(vap);               //把指针vap赋值为0
    return sum;
}
int main(void)
{
    int m = sum(3 , 45 , 89 , 72);
    cout<<m<<endl;
    return 0;
}  

注意:

int f(...)
{
    ......
    ......
    ......
}  

不能这样定义,因为这是ANSI C 所要求的,至少得定义一个固定参数。这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值

例子:

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

void myitoa(int n, char str[], int radix)  //将整数表达成字符形态
{
    int i , j , remain;
    char tmp;
    i = 0;
    do                      //例如n = 25,radix = 10(表达成10进制),      
    {
        remain = n % radix;
        if(remain > 9)
            str[i] = remain  - 10 + ‘A‘;  //为了十六进制,10将表示成A
        else
            str[i] = remain + ‘0‘;    //将整数+‘0‘ = 整数对应的ASCII码
        i++;
    }while(n /= radix);      
    str[i] = ‘\0‘;              

    for(i-- , j = 0 ; j <= i ; j++ , i--)  //25%10 = 5,25/10 = 2,2%10 = 2,2/10 = 0,所以str中结果是倒置的,翻转一下
    {
        tmp = str[j];
        str[j] = str[i];
        str[i] = tmp;
    }  

}  

void myprintf(const char *format, ...)
{
    char c, ch, str[30];
    va_list ap;  

    va_start(ap, format);
    while((c = *format))
    {
        switch(c)
        {
        case ‘%‘:            
            ch = *++format;
            switch(ch)
            {
            case ‘d‘:                //%d
                {
                    int n = va_arg(ap, int);  //表明可变参数中有整数,输出
                    myitoa(n, str, 10);
                    fputs(str, stdout);
                    break;
                }
            case ‘x‘:                //%x十六进制输出整数
                {
                    int n = va_arg(ap, int);
                    myitoa(n, str, 16);
                    fputs(str, stdout);
                    break;
                }
            case ‘f‘:
                {
                    double f = va_arg(ap, double);  //%f,输出浮点数
                    int n;
                    n = f;
                    myitoa(n, str, 10);        //把浮点数拆分成整数和小数部分,然后小数部分乘上1e6成整数再输出
                    fputs(str, stdout);
                    putchar(‘.‘);
                    n = (f - n) * 1000000;
                    myitoa(n, str, 10);
                    fputs(str, stdout);
                    break;
                }
            case ‘c‘:
                {
                    putchar(va_arg(ap, int));
                    break;
                }
            case ‘s‘:
                {
                    char *p = va_arg(ap, char *);      //输出字符串
                    fputs(p, stdout);
                    break;
                }
            case ‘%‘:                      //%%  输出%
                {
                    putchar(‘%‘);
                    break;
                }
            default:
                {
                    fputs("format invalid!", stdout);
                    break;
                }
            }
            break;
        default:
            putchar(c);
            break;
        }
        format++;
    }
    va_end(ap);
}  

int main(void)
{
    myprintf("%d, %x, %f, %c, %s, %%,%a\n", 10, 15, 3.14, ‘B‘, "hello");
    return 0;
}  

超级大栗子:

//没有输出%lf和%f格式

#include "console.h"
#include "string.h"
#include "vargs.h"
#include "debug.h"

static int vsprintf(char *buff, const char *format, va_list args);

void printk(const char *format, ...)
{
    // 避免频繁创建临时变量,内核的栈很宝贵
    static char buff[1024];
    va_list args;
    int i;

    va_start(args, format);
    i = vsprintf(buff, format, args);
    va_end(args);

    buff[i] = ‘\0‘;

    console_write(buff);
}

void printk_color(real_color_t back, real_color_t fore, const char *format, ...)
{
    // 避免频繁创建临时变量,内核的栈很宝贵
    static char buff[1024];
    va_list args;
    int i;

    va_start(args, format);
    i = vsprintf(buff, format, args);
    va_end(args);

    buff[i] = ‘\0‘;

    console_write_color(buff, back, fore);
}

#define is_digit(c)    ((c) >= ‘0‘ && (c) <= ‘9‘)

static int skip_atoi(const char **s)  //把字符表述的整型数字转化成真正的整型
{
    int i = 0;

    while (is_digit(**s)) {
        i = i * 10 + *((*s)++) - ‘0‘;
    }

    return i;
}

#define ZEROPAD        1        // pad with zero用0填补         ‘%04d’  4位宽度,不够前面补零
#define SIGN         2       // unsigned/signed long  ‘%d和%i  , %u(有符号)’
#define PLUS        4        // show plus           ‘+’
#define SPACE          8       // space if plus       ‘ ’
#define LEFT         16      // left justified        ‘-’,左对齐即数据先输出,不够宽度用指定符号补齐
#define SPECIAL        32      // 0x                  ‘#’
#define SMALL          64      // use ‘abcdef‘ instead of ‘ABCDEF‘
//注意此处数字设计,1 = 0000001,2 = 0000010, 4 = 0000100 ,8 = 0001000...
//6位中每一位对应一种格式,有则为1

#define do_div(n,base) ({         int __res;         __asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \  //(ax)/ (%4) = n/base
        __res; }) //返回商_res,divl长字4字节,刚好符合下面的int
//32位相除,商在EAX,余数在EDX,#define a ({1;2;3;})奇怪的表达调用a ,即为3,同理调用do_div,即为_res值

static char *number(char *str, int num, int base, int size/*field_width*/, int precision, int type/*flag*/)
{
    char c, sign, tmp[36];
    const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int i;

    if (type & SMALL) {    //用小写
        digits ="0123456789abcdefghijklmnopqrstuvwxyz";
    }
    if (type & LEFT) {    //如果flag既有左对齐,又有用0来补,此处指明数位不够,则用左对齐规则(用空格补),不用0补
        type &= ~ZEROPAD;
    }
    if (base < 2 || base > 36) {
        return 0;
    }//base = 2, 10, 8,16

    c = (type & ZEROPAD) ? ‘0‘ : ‘ ‘ ;//如果flag既有左对齐,又有用0来补,上面吧ZEROPAD删去了,所以用空格

    if (type & SIGN && num < 0) {//    +        输出符号(正号或负号)
        sign = ‘-‘;                //    空格        输出值为正时冠以空格,为负时冠以负号
        num = -num;                //
    } else {
        sign = (type&PLUS) ? ‘+‘ : ((type&SPACE) ? ‘ ‘ : 0);//num可能为0和正数,有PLUS那么输出带+,没有PLUS有空格输出带空格,没有空格那就是为0
    }                                                        

    if (sign) {    //有正负号,占去一位宽度
          size--;
    }
    if (type & SPECIAL) {    //有SPECIAL
        if (base == 16) {
            size -= 2;        //输出前缀0x占去两位
        } else if (base == 8) {//输出前缀o
            size--;
        }
    }
    i = 0;
    if (num == 0) {
        tmp[i++] = ‘0‘;
    } else {
        while (num != 0) {
            tmp[i++] = digits[do_div(num,base)];//假如num为25,tmp从0开始存着‘5’,‘2’
        }
    }

    if (i > precision) {//精度限制,主要用于小数点后几位(虽然没有)
        precision = i;
    }
    size -= precision;//如果表达的是带小数的整数,size剩下为整数部分位数

    if (!(type&(ZEROPAD+LEFT))) {//ZEROPAD和LEFT都没有
        while (size-- > 0) {
            *str++ = ‘ ‘;
        }
    }
    if (sign) {    //带符号的附上正负号
        *str++ = sign;
    }
    if (type & SPECIAL) {
        if (base == 8) {//八进制带上0
            *str++ = ‘0‘;
        } else if (base == 16) {//十六进制带上0x
            *str++ = ‘0‘;
            *str++ = digits[33];
        }
    }
    if (!(type&LEFT)) { //没有LEFT
        while (size-- > 0) {
            *str++ = c;
        }
    }
    while (i < precision--) {//用0补齐到指定宽度
        *str++ = ‘0‘;
    }
    while (i-- > 0) {      //反着到给str,num25,10进制,tmp中存‘5’‘2’,现在str中变成25
        *str++ = tmp[i];
    }
    while (size-- > 0) {//用空格补齐到指定宽度
        *str++ = ‘ ‘;
    }

    return str;
}

static int vsprintf(char *buff, const char *format, va_list args)
{
    int len;
    int i;
    char *str;
    char *s;
    int *ip;

    int flags;        // flags to number()

    int field_width;    // width of output field    输出结果宽度
    int precision;        // min. # of digits for integers; max number of chars for from string
                        //输出精度,确定小数点后多少位(虽然没有)和字符串长度

    for (str = buff ; *format ; ++format) {
        if (*format != ‘%‘) {
            *str++ = *format;
            continue;
        }

        flags = 0; //*format = ‘%‘
        repeat:
            ++format;        // this also skips first ‘%‘ ++format跳过‘%‘
            switch (*format) {
                case ‘-‘: flags |= LEFT;    //输出宽度为4,数据为16 ,输出结果为“16  ”(补上俩空格)
                      goto repeat;
                case ‘+‘: flags |= PLUS;
                      goo repeat;
                case ‘ ‘: flags |= SPACE;
                      goto repeat;
                case ‘#‘: flags |= SPECIAL;
                      goto repeat;
                case ‘0‘: flags |= ZEROPAD;
                      goto repeat;
            }
    //    -        结果左对齐,右边填空格
    //    +        输出符号(正号或负号)
    //    空格        输出值为正时冠以空格,为负时冠以负号
    //    #        对c、s、d、u类无影响;
    //            对o类,在输出时加前缀o;
    //            对x类,在输出时加前缀0x;
    //            对e、g、f 类当结果有小数时才给出小数点。
    //    0        printf("%04d", 16);输出数据0016,宽度为4    

        // get field width
        field_width = -1; //
        if (is_digit(*format)) {                //例如%15d,指定输出宽度为15,用空格来补
            field_width = skip_atoi(&format);    //例如%010,skip_atoi返回10,定义输出数据宽度
        } else if (*format == ‘*‘) {            //例如printf("%*d", 4, 16); 指定输出宽度为4,不够用空格补
            // it‘s the next argument
            field_width = va_arg(args, int);
            if (field_width < 0) {                //如果printf("%*d", -7, 16);那么那个负号就相当于指定左对齐‘-‘,然后7表示输出宽度为7,用空格补
                field_width = -field_width;
                flags |= LEFT;
            }
        }

        // get the precision
        precision = -1;                //此处的precision主要是用于字符串,不用于小数点后几位,因为没有
        if (*format == ‘.‘) {   //%5.4lf指定输出宽度为5,精度为4,如果数据实际长度超过5(123.1234567)
            ++format;            //故应该按实际位数输出,小数位数超过4位部分被截去“123.1234”
            if (is_digit(*format)) {
                precision = skip_atoi(&format);
            } else if (*format == ‘*‘) {        //根据传入实参指定精度
                // it‘s the next argument
                precision = va_arg(args, int);
            }
            if (precision < 0) {
                precision = 0;
            }
        }

        // get the conversion qualifier
        //int qualifier = -1;    // ‘h‘, ‘l‘, or ‘L‘ for integer fields
        if (*format == ‘h‘ || *format == ‘l‘ || *format == ‘L‘) {   // %ld   表示输出long整数
            //qualifier = *format;                                    // %lf   表示输出double浮点数
            ++format;
        }

        switch (*format) {
        case ‘c‘:                    //字符
            if (!(flags & LEFT)) {  //没有LEFT,最后输出数据
                while (--field_width > 0) {
                    *str++ = ‘ ‘;
                }
            }
            *str++ = (unsigned char) va_arg(args, int);
            while (--field_width > 0) {    //有LEFT,无需else,因为如果有LEFT,上面已将field_width减成0
                *str++ = ‘ ‘;
            }
            break;

        case ‘s‘:                    //字符串
            s = va_arg(args, char *);
            len = strlen(s);    //根据精度来确定输出字符串长度
            if (precision < 0) {
                precision = len;
            } else if (len > precision) {
                len = precision;
            }

            if (!(flags & LEFT)) {            //没有LEFT,最后输出数据
                while (len < field_width--) {
                    *str++ = ‘ ‘;
                }
            }
            for (i = 0; i < len; ++i) {
                *str++ = *s++;
            }
            while (len < field_width--) {//补齐到宽度
                *str++ = ‘ ‘;
            }
            break;

        case ‘o‘:            //八进制整数
            str = number(str, va_arg(args, unsigned long), 8,
                field_width, precision, flags);
            break;

        case ‘p‘:            //%p输出指针的值
            if (field_width == -1) {
                field_width = 8;
                flags |= ZEROPAD;
            }
            str = number(str, (unsigned long) va_arg(args, void *), 16,
                field_width, precision, flags);
            break;

        case ‘x‘:                        //%x, %X无符号以十六进制表示的整数,%x:16进制中为:abcdef,%x:ABCDEF
            flags |= SMALL;                //没有break呦!!!
        case ‘X‘:
            str = number(str, va_arg(args, unsigned long), 16,
                field_width, precision, flags);
            break;

        case ‘d‘:
        case ‘i‘:
            flags |= SIGN;    //%d,%i加上十进制符号整数
        case ‘u‘:            // %u十进制无符号整数
            str = number(str, va_arg(args, unsigned long), 10,
                field_width, precision, flags);
            break;
        case ‘b‘:            //实际上printf不提供输出二进制
            str = number(str, va_arg(args, unsigned long), 2,
                field_width, precision, flags);
            break;

        case ‘n‘:
            ip = va_arg(args, int *);
            *ip = (str - buff);    //记录输出的数据长度???
            break;

        default:
            if (*format != ‘%‘)
                *str++ = ‘%‘;
            if (*format) {
                *str++ = *format;    //屁精屁精的,比如像%%,第一个if不进,进第二个加入%,其他的%w,w != %,str加入%,w进第二个if,str加入w
            } else {            //没想到特殊情况=_=
                --format;
            }
            break;
        }
    }
    *str = ‘\0‘;

    return (str - buff);    //输出结果长度
}

注释已经加上,慢慢品味内核中的printk函数  (T  。T)!!!

Done!!!

引用:

http://blog.csdn.net/hackbuteer1/article/details/7558979

时间: 2024-07-30 03:23:45

vsprintf解析的相关文章

perl核心模块解析(一)POSIX

perl核心模块解析(一)    POSIX 详细文档链接如下 源自:http://blog.csdn.net/jonathanxqs 转自:http://perldoc.perl.org/POSIX.html POSIX/?p?z?ks/, 是可移植操作系统接口(Portable Operating System Interface ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE

MTK Http Socket GPRS以及解析器和下载器概述

MTK App网络应用介绍 MTK App网络开发步骤 GPRS网络注册模块 Socket联网API接口 HTTP功能部件 使用HTTP封装解析器和下载器 Parser Downloader 调试功能 应用需要使用的声明文件 配置功能 MTK平台组件介绍 Timer MTK App网络应用介绍 MTK手机App开发过去开发总是使用while循环嵌套来获取数据,而现在我们使用消息机制来完成Socket上网.本篇博客借手机里的天气应用来系统介绍MTK手机App连接上网所需要经历的步骤. MTK Ap

C++工程编译之“error LNK2001: 无法解析的外部符号”

今天一整天都在折腾“error LNK2001: 无法解析的外部符号”,就在头疼不已的时候,总算是找到问题原因了:各个动态链接库的编译方式必须统一才行,要不然很容易对库函数的引用产生冲突.简单来说就是,如果使用的第三方函数库编译方式采用/MD,那么主工程也应该使用/MD.我使用了libevent,而主工程默认采用/MT,所以需要忽略一大堆的函数库,我还纳闷呢,怎么会这么奇怪!!今天总算是解决了长久以来的困惑了. 下面引用一篇文章的描述:[Z]VC运行库版本不同导致链接.LIB静态库时发生重复定义

防止恶意解析——禁止通过IP直接访问网站

一.什么是恶意解析 一般情况下,要使域名能访问到网站需要两步,第一步,将域名解析到网站所在的主机,第二步,在web服务器中将域名与相应的网站绑定.但是,如果通过主机IP能直接访问某网站,那么把域名解析到这个IP也将能访问到该网站,而无需在主机上绑定,也就是说任何人将任何域名解析到这个IP就能访问到这个网站.可能您并不介意通过别人的域名访问到您的网站,但是如果这个域名是未备案域名呢?一旦被查出,封IP.拔线甚至罚款的后果都是需要您来承担的.某些别有用心的人,通过将未备案域名解析到别人的主机上,使其

.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口详解)

阅读目录: 1.环路执行对象模型.碎片化执行模型(假递归式调用) 2.N层对象执行模型(纵横向对比链式扩展方法) 3.LINQ查询表达式和链式查询方法其实都是空壳子 4.详细的对象结构图(对象的执行原理) 5.IQueryable<T>与IQueryProvider一对一的关系能否改成一对多的关系 6.完整的自定义查询 1]. 环路执行对象模型.碎片化执行模型(假递归式调用) 这个主题扯的可能有点远,但是它关系着整个LINQ框架的设计结构,至少在我还没有搞懂LINQ的本意之前,在我脑海里一直频

.NET深入解析LINQ框架(一:LINQ优雅的前奏)

阅读目录: 1.LINQ简述 2.LINQ优雅前奏的音符 2.1.隐式类型 (由编辑器自动根据表达式推断出对象的最终类型) 2.2.对象初始化器 (简化了对象的创建及初始化的过程) 2.3.Lambda表达式 (对匿名方法的改进,加入了委托签名的类型推断并很好的与表达式树的结合) 2.4.扩展方法 (允许在不修改类型的内部代码的情况下为类型添加独立的行为) 2.5.匿名类型 (由对象初始化器推断得出的类型,该类型在编译后自动创建) 2.6.表达式目录树(用数据结构表示程序逻辑代码) 3.LINQ

.NET深入解析LINQ框架(二:LINQ优雅的前奏)

阅读目录: 1.LINQ框架的主要设计模型 1.1.链式设计模式 (以流水线般的链接方式设计系统逻辑) 1.2.链式查询方法(逐步加工查询表达式中的每一个工作点) 2.LINQ框架的核心设计原理 2.1.托管语言之上的语言(LINQ查询表达式) 2.2.托管语言构造的基础(LINQ依附通用接口与查询操作符对应的方法对接) 2.3.深入IEnumerable.IEnumerable<T>.Enumerable(LINQ to Object框架的入口) 2.4.深入IQueryable.IQuer

.NET深入解析LINQ框架(三:LINQ优雅的前奏)

阅读目录: 1.动态LINQ查询(动态构建Expression<T>表达式树) 2.DLR动态语言运行时(基于CLR之上的动态语言运行时) 1].动态LINQ查询(动态构建Expression<T>表达式树) 什么是动态LINQ查询?LINQ的编写是静态的,因为C#是基于静态类型系统原理设计的,在编写时已经确定类型,也就是在编译时就已经知道将要执行什么样的查询,条件是什么.排序方式是什么等等.那么很大一部分应用场合中我们需要根据用户的选择来查询数据源,以往我们都是通过判断的方式来拼

.NET深入解析LINQ框架(六:LINQ执行表达式)

阅读目录: 1.LINQ执行表达式 在看本篇文章之前我假设您已经具备我之前分析的一些原理知识,因为这章所要讲的内容是建立在之前的一系列知识点之上的,为了保证您的阅读顺利建议您先阅读本人的LINQ系列文章的前几篇或者您已经具备比较深入的LINQ原理知识体系,防止耽误您的宝贵时间. 到目前为止我们对LINQ的执行原理已经很清楚了,从它的前期构想到它真正为我们所用都有足够的证据,但是似乎问题并没有我们想的那么简单,问题总是在我们使用中频频出现尤其是新技术的使用,当然有问题才能有进步. 一:LINQ执行