linux内核分析之vsprintf.c

2024-04-20 20:38
文章标签 分析 linux 内核 vsprintf

本文主要是介绍linux内核分析之vsprintf.c,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

#include <stdarg.h>
#include <string.h>

判断给定字符是不是0-9的数字
#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  
#define SIGN 2  
#define PLUS 4  
#define SPACE 8  
#define LEFT 16  
#define SPECIAL 32  
#define SMALL 64

内嵌汇编,定义除法运算
输出寄存器是eax和edx,一个存放商一个存放余数
除数是base,被除数你放在eax中
#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";
 int i;
  如果是小写则把数字字母转换表改写
 if (type&SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz";
 如果是左对齐,需要把ZEROPAD标志去掉
 if (type&LEFT) type &= ~ZEROPAD;
 
 检查基数范围是否正确。
 if (base<2 || base>36)
  return 0;
  如果设置了标志位ZEROPAD,则表示用0去填充他,否则用空格
 c = (type & ZEROPAD) ? '0' : ' ' ;
  如果设置了符号位,并且数字是负数,需要加上符号字符
 if (type&SIGN && num<0) {
  sign='-';
  num = -num;
 } else
  如果是标志位PLUS设置,需要在正数前面加上+号,否则
  如果设置了SPACE,则在符号位用空格填充,要不就用0填充
  sign=(type&PLUS) ? '+' : ((type&SPACE) ? ' ' : 0);
 如果符号位被设置成非0值,需要将size大小减去1
 if (sign) size--;
 如果设置了SPECIAL标志位,需要在八进制前面加O字符,需要在十六进制前面加0x字符串,
 并相应的调整size的大小
 if (type&SPECIAL)
  if (base==16) size -= 2;
  else if (base==8) size--;
 i=0;
  将数值部分转换成字符串存入tmp中。
 if (num==0)
  tmp[i++]='0';
 else while (num!=0)
  tmp[i++]=digits[do_div(num,base)];
  如果超过了精度规定的长度,则重置精度值
 if (i>precision) precision=i;
 调整size的值
 size -= precision;
 如果既没设置ZEROPAD标志,也没设置LEFT标志,将剩下的部分用空格填充,也就是将字符串最开始的size个字符位设置
 为空格
 if (!(type&(ZEROPAD+LEFT)))
  while(size-->0)
   *str++ = ' ';
 接着设置符号位
 if (sign)
  *str++ = sign;
 如果是八进制或者十六进制,需要设置0或者0x字符
 if (type&SPECIAL)
  if (base==8)
   *str++ = '0';
  else if (base==16) {
   *str++ = '0';
   *str++ = digits[33];
  }
 如果没有设置左对齐,则需要进行填充
 if (!(type&LEFT))
  while(size-->0)
   *str++ = c;
 填充精度范围部分,用‘0’进行填充
 while(i<precision--)
  *str++ = '0';
 设置数字部分
 while(i-->0)
  *str++ = tmp[i];
 用空格填剩余部分
 while(size-->0)
  *str++ = ' ';
 return str;
}

格式化输出:%[flags][width][.precision][|h|l|L][type]
int vsprintf(char *buf, const char *fmt, va_list args)
{
 int len;
 int i;
 char * str;
 char *s;
 int *ip;

 int flags;  

 int field_width; 
 int precision;  
 int qualifier;
 
  对格式字符串进行扫描,把va_list中的数进行格式化,存入str中
 for (str=buf ; *fmt ; ++fmt) {
  如果不是%号,那么仅仅做简单的付值
  if (*fmt != '%') {
   *str++ = *fmt;
   continue;
  }
  如果是%号 
  flags = 0;
  repeat:
   ++fmt;
   判断flags,并设置相应的位
   switch (*fmt) {
    case '-': flags |= LEFT; goto repeat;
    case '+': flags |= PLUS; goto repeat;
    case ' ': flags |= SPACE; goto repeat;
    case '#': flags |= SPECIAL; goto repeat;
    case '0': flags |= ZEROPAD; goto repeat;
    }
  
  field_width = -1;
  紧接着判断下一个字符是不是数字,如果是则表示域宽
  否则如果是*,表示下一个参数指定域宽,用va_arg来取得
  下一个参数值
  if (is_digit(*fmt))
   field_width = skip_atoi(&fmt);
  else if (*fmt == '*') {
   field_width = va_arg(args, int);
   if (field_width < 0) {
    field_width = -field_width;
    flags |= LEFT;
   }
  }
    取得精度
  precision = -1;
  如果下一个字符是‘.’,表示有精度值,同样精度值如果是一个数字
  就直接表示精度了,如果是字符*,表示从参数列表中取得精度
  if (*fmt == '.') {
   ++fmt; 
   if (is_digit(*fmt))
    precision = skip_atoi(&fmt);
   else if (*fmt == '*') {
    precision = va_arg(args, int);
   }
   if (precision < 0)
    precision = 0;
  }

    取修饰符
  qualifier = -1;
  if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
   qualifier = *fmt;
   ++fmt;
  }
   
    最后取得格式
  switch (*fmt) {
  如果格式化成字符
  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 (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':
   如果没有设置域宽,则需要设置域宽为8,0填充标志
   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':
   flags |= SMALL;
  case 'X':
   str = number(str, va_arg(args, unsigned long), 16,
    field_width, precision, flags);
   break;

    如果格式化成整型
  case 'd':
  带符号数
  case 'i':
   flags |= SIGN;
  case 'u':
   str = number(str, va_arg(args, unsigned long), 10,
    field_width, precision, flags);
   break;
    将到目前为止转换的字符个数保存到ip指针指定的位置
  case 'n':
   ip = va_arg(args, int *);
   *ip = (str - buf);
   break;
   
  default:
   如果格式转换符不是%,表示有错,将一个%写入,如果格式字符串还没遍历完
   直接将字符写入,否则退出循环
   if (*fmt != '%')
    *str++ = '%';
   if (*fmt)
    *str++ = *fmt;
   else
    --fmt;
   break;
  }
 }
 *str = '/0';
 return str-buf;
}

这篇关于linux内核分析之vsprintf.c的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921266

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺