vsprintf解析

Posted chaunceyctx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于vsprintf解析的主要内容,如果未能解决你的问题,请参考以下文章

PHP中函数sprintf .vsprintf (占位符)

确定vsprintf输出的大小

片段(Java) | 机试题+算法思路+考点+代码解析 2023

stdio 函数(sprintf、vsprintf 和 fprintf)背后的命名约定是啥?

使用addr2line 定位 FORTIFY: vsprintf: prevented write past end of buffer‘ 报错解决思路

使用addr2line 定位 FORTIFY: vsprintf: prevented write past end of buffer‘ 报错解决思路