从零实现一个操作系统-day8

本文详细介绍了一个简易版printf函数的实现过程,包括如何解析格式字符串、处理标志位、宽度和精度,以及不同类型数据的输出方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我的博客startcraft

实现简易版的printf函数

屏幕的输入输出函数主要的功能还是dubug,我们模仿标准库来实现,标准库的printf基于vsprintf

int vsprintf(const char *format, va_list arg)

先看看printk的内容

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

	va_start(args, format);
	int 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;

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

	buff[i] = '\0';

	console_write_color(buff, back, fore);
}

可以看出vsprint返回值是输出的字符个数,它的工作过程是格式化要输出的字符串,buff最后就是可以输出的字符串

vprintf

参考:https://www.cnblogs.com/zhoug2020/p/7676586.html
格式字符串的格式如下

%[flags][width][.prec][length]type
flags


flags的格式如上图,我们可以在程序中用二进制位来表示,先来定义这些标志的二进制位

#define ZEROPAD		1	//将输出的前面补上0
#define SIGN		2	// unsigned/signed long
#define PLUS		4	// 显示加号
#define SPACE		8	// 正数前面输出空格,负数输出负号
#define LEFT		16	// 左对齐
#define SPECIAL		32	// '#'标志位
#define SMALL		64	// 16进制使用'abcdef'而不是'ABCDEF'

开始实现vsprintf的flags部分的判断

for (;*format;++format)
	{
		if(*format!='%')
		{
			*str++=*format;
			continue;
		}
		flags=0;
		repate:
			format++;
			switch(*format)
			{
				case '-':
					flags|=LEFT;
					goto repate;
				case '+':
					flags|=PLUS;
					goto repate;
				case ' ':
					flags|=SPACE;
					goto repate;
				case '#':
					flags|=SPECIAL;
					goto repate;
				case '0':
					flags|=ZEROPAD;
					goto repate;
			}
	}

这一段就是将标志位对应的那一段置1,当然如果没有’%'就直接输出就完事了

width

用十进制整数来表示输出的最少位数。若实际位数多于指定的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。width的可能取值如下:

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

static int skip_aoti(const char **s)
{
	int i=0;
	while(is_digit(**s))
	i=i*10+*((*s)++)-'0';
	return i;
}

//获取域宽
field_width=-1;
if (is_digit(*format))
	field_width=skip_aoti(&format);
else if(*format=='*')
{
	field_width=va_arg(args,int);
	if(field_width<0)
	{
		field_width=-field_width;
		flags|=LEFT;
	}
}

如果是一个数字,那么直接将字符转换成数字就是域宽,如果是*号,那么后一个参数就是域宽,如果后一个参数是负数就理解是左对齐的符号和域宽放在了一起
为什么 s k i p a t o i skip_atoi skipatoi的参数是 c o n s t c h a r ∗ ∗ const char** constchar ,首先因为 f o r m a t format format c o n s t const const,所以它也是 c o n s t const const,然后我们需要改变 f o r m a t format format的值,那么参数就得是 f o r m a t format format的指针,所以是 c h a r ∗ ∗ char** char

.prec

精度格式符以“.”开头,后跟十进制整数。可取值如下:

//获取精度
precision=-1;
if(*format=='.')
{
	++format;
	if(is_digit(*format))
		precision=skip_aoti(&format);
	else if(*format=='*')
	{
		precision=va_arg(args,int);
	}
	if(precision<0)
		precision=0;
}

处理跟获取域宽差不多,但是负值就忽略了

type

length我们就直接忽略了,毕竟是简易的printf,type有很多,我们一个个来

c(字符)
//输出字符
case 'c':
	if(!(flags&LEFT))//右对齐
	{
		while(--field_width>0)
			*str++ =' ';
	}
	*str++ =(unsigned char) va_arg(args,int);//获取要输出的字符
	while(--field_width>0)
		*str++ =' ';
	break;

先判断左对齐的标志位是否为1,不然就是右对齐要在左边填空格直到长度为域宽-1

s(字符串)
//输出字符串
case 's':
	s=va_arg(args,char*);
	len=strlen(s);
	if(precision<0)//代表未指定
		precision=len;
	else if(len>precision)
		len=precision;
	if(!(flags&LEFT))
	{
		while(--field_width)
			*str++ =' ';
	}
	for(int i=0;i<len;++i)
		*str++ = *s++;
	while(len<field_width--)
		*str++ =' ';
	break;

%ms:输出的字符串占m列,如字符串本身长度大于m,则突破获m的限制,将字符串全部输出。若串长小于m,则左补空格。
%-ms:如果串长小于m,则在m列范围内,字符串向左靠,右补空格。
%m.ns:输出占m列,但只取字符串中左端n个字符。这n个字符输出在m列的右侧,左补空格,注意:如果n未指定,默认为0.
%-m.ns:其中m、n含义同上,n个字符输出在m列范围的左侧,右补空格。如果n>m,则自动取n值,即保证n个字符正常输出,注意:如果n未指定,默认为0.

o (无符号8进制(octal)整数(不输出前缀0))

对于整数的输出比较复杂,所以写了一个number函数来构造输出的字符串

static char *number(char *str, int num, int base, int size, int precision, int type)

base是进制,type就是flags规定输出的格式

#define do_div(n,base) ({ int __res;\
	__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base));\
	__res; })
static char *number(char *str, int num, int base, int size, int precision, int type)
{
    char c,sign,tmp[36];
    const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//0-36进制数的字符集
    int i;
    if(type&SMALL)//若规定输出小写字母字符集就是这个
        digits ="0123456789abcdefghijklmnopqrstuvwxyz";
    if(type&LEFT)
        type&= ~ZEROPAD;//即‘0’和‘-’不能同时使用
    if(base<2||base>36)
        return 0;
    c=(type&ZEROPAD)? '0':' ';//判断填充满域宽的应该是什么字符
    if((type&SIGN)&&num<0)//若输出的是有符号数且为负数,则符号设为负号
    {
        sign='-';
        num=-num;
    }else 
        sign=(type&PLUS)? '+': (type&SPACE)? ' ' : 0;//决定正数左端的符号
    if(sign)
        size--;//如果有符号
    if(type&SPECIAL){//type为井号
        if(base==16)//16进制前面输出0x或0X
            size-=2;
        else if(base==8)//8进制前面输出0
            size--;
    }
    i=0;
    if(num==0)    
        tmp[i++]='0';
    else
    {
        //进制转换
        while(num!=0)
            tmp[i++]=digits[do_div(num,base)];
    }
    if(i>precision)
        precision=i;
    size-=precision;
    if(!(type&(ZEROPAD+LEFT)))//既不填0也不左对齐,那么填充的是空格
    {
        while(size-- >0)
            *str++ =' ';
    }
    if(sign)
        *str++ =sign;//加上符号
    if(type&SPECIAL)
    {
        if(base==8)
            *str++ ='0';//八进制前面加0
        else if(base==16)
        {
            //16进制前加0x或0X
            *str++ ='0';
            *str++ =digits[33];
        }
    } 
    if(!(type&LEFT))//如果部署左对齐,左端要填上对应的填充符号
    {
        while(size-- >0)
            *str++=c;
    }
    while(i<precision--)//精度不够的话,因为是整数所以要在左端加0
        *str++ ='0';
    while(i-- >0)
    {
        *str++=tmp[i];
    }
    while(size-- >0)
        *str++=' ';
    return str;
}

然后输出无符号八进制整数就很简单了

case 'o':
	str=number(str,va_arg(args,unsigned long),8,field_width,precision,flags);
	break;
p (以16进制形式输出指针)
case 'p':
	if(field_width ==-1)
	{
		field_width=8;//默认32位
		flags|=ZEROPAD;
	}
	str=number(str,(unsigned long)va_arg(args,void*),16,field_width,precision,flags);
	break;
x和X (16进制大小写)
case 'x':
	flags|=SMALL;
case 'X':
	str=number(str,va_arg(args,unsigned long),16,field_width,precision,flags);
	break;
i和d和u (有符号32位整数和无符号32位整数)
case 'i':
case 'd':
	flags|=SIGN;
case 'u':
	str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
	break;
b (二进制)
case 'b':
	str=number(str,va_arg(args,unsigned long),2,field_width,precision,flags);
	break;
n (什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置)
case 'n':
	ip=va_arg(args,int *);
	*ip=(str-buff);
	break;

最后完整的printk代码

kernel/debug/printk.c

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

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

void printk (char *format, ...)
{
    // 避免频繁创建临时变量,内核的栈很宝贵
    static char buff[1024];
    va_list args;
    va_start(args,format);
    int 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;
    va_start(args,format);
    int 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')

#define ZEROPAD     1   //将输出的前面补上0
#define SIGN        2   // unsigned/signed long
#define PLUS        4   // 显示加号
#define SPACE       8   // 正数前面输出空格,负数输出负号
#define LEFT        16  // 左对齐
#define SPECIAL     32  // '#'标志位
#define SMALL       64  // 16进制使用'abcdef'而不是'ABCDEF'


#define do_div(n,base) ({ int __res; __asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); __res; })

static int skip_aoti(const char **s)
{
    int i=0;
    while(is_digit(**s))
        i=i*10+*((*s)++)-'0';
    return i;
}

static char *number(char *str, int num, int base, int size, int precision, int type)
{
    char c,sign,tmp[36];
    const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//0-36进制数的字符集
    int i;
    if(type&SMALL)//若规定输出小写字母字符集就是这个
        digits ="0123456789abcdefghijklmnopqrstuvwxyz";
    if(type&LEFT)
        type&= ~ZEROPAD;//即‘0’和‘-’不能同时使用
    if(base<2||base>36)
        return 0;
    c=(type&ZEROPAD)? '0':' ';//判断填充满域宽的应该是什么字符
    if((type&SIGN)&&num<0)//若输出的是有符号数且为负数,则符号设为负号
    {
        sign='-';
        num=-num;
    }else 
        sign=(type&PLUS)? '+': (type&SPACE)? ' ' : 0;//决定正数左端的符号
    if(sign)
        size--;//如果有符号
    if(type&SPECIAL){//type为井号
        if(base==16)//16进制前面输出0x或0X
            size-=2;
        else if(base==8)//8进制前面输出0
            size--;
    }
    i=0;
    if(num==0)    
        tmp[i++]='0';
    else
    {
        //进制转换
        while(num!=0)
            tmp[i++]=digits[do_div(num,base)];
    }
    if(i>precision)
        precision=i;
    size-=precision;
    if(!(type&(ZEROPAD+LEFT)))//既不填0也不左对齐,那么填充的是空格
    {
        while(size-- >0)
            *str++ =' ';
    }
    if(sign)
        *str++ =sign;//加上符号
    if(type&SPECIAL)
    {
        if(base==8)
            *str++ ='0';//八进制前面加0
        else if(base==16)
        {
            //16进制前加0x或0X
            *str++ ='0';
            *str++ =digits[33];
        }
    } 
    if(!(type&LEFT))//如果部署左对齐,左端要填上对应的填充符号
    {
        while(size-- >0)
            *str++=c;
    }
    while(i<precision--)//精度不够的话,因为是整数所以要在左端加0
        *str++ ='0';
    while(i-- >0)
    {
        *str++=tmp[i];
    }
    while(size-- >0)
        *str++=' ';
    return str;
}

static int vsprintf (char *buff, const char *format, va_list args)
{
    int *ip;
    char *str=buff;
    int len;
    int flags;//标志位
    char *s;
    int field_width;
    int precision;
    for (;*format;++format)
    {
        if(*format!='%')
        {
            *str++=*format;
            continue;
        }
        flags=0;
        repate:
            format++;
            switch(*format)
            {
                case '-':
                    flags|=LEFT;
                    goto repate;
                case '+':
                    flags|=PLUS;
                    goto repate;
                case ' ':
                    flags|=SPACE;
                    goto repate;
                case '#':
                    flags|=SPECIAL;
                    goto repate;
                case '0':
                    flags|=ZEROPAD;
                    goto repate;
            }
        //获取域宽
        field_width=-1;
        if (is_digit(*format))
            field_width=skip_aoti(&format);
        else if(*format=='*')
        {
            field_width=va_arg(args,int);
            if(field_width<0)
            {
                field_width=-field_width;
                flags|=LEFT;
            }
        }
        //获取精度
        precision=-1;
        if(*format=='.')
        {
            ++format;
            if(is_digit(*format))
                precision=skip_aoti(&format);
            else if(*format=='*')
            {
                precision=va_arg(args,int);
            }
            if(precision<0)
                precision=0;
        }
        if (*format=='h' || *format=='l'||*format=='L')
            ++format;

        switch (*format)
        {
            //输出字符
            case 'c':
                if(!(flags&LEFT))//右对齐
                {
                    while(--field_width>0)
                        *str++ =' ';
                }
                *str++ =(unsigned char) va_arg(args,int);//获取要输出的字符
                while(--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))
                {
                    while(--field_width)
                        *str++ =' ';
                }
                for(int 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':
                if(field_width ==-1)
                {
                    field_width=8;//默认32位
                    flags|=ZEROPAD;
                }
                str=number(str,(unsigned long)va_arg(args,void*),16,field_width,precision,flags);
                break;
            case 'x':
                flags|=SMALL;
            case 'X':
                str=number(str,va_arg(args,unsigned long),16,field_width,precision,flags);
                break;
            case 'i':
            case 'd':
                flags|=SIGN;
            case 'u':
                str=number(str,va_arg(args,unsigned long),10,field_width,precision,flags);
                break;
            case 'b':
                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;
                } else {
                    --format;
                }
                break;
        }
    }
    *str++='\0';
    return (str-buff);
}

现在的目录结构是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值