前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >日志模块--手动实现printf函数demo

日志模块--手动实现printf函数demo

作者头像
Ch_Zaqdt
发布2020-03-23 17:31:06
8010
发布2020-03-23 17:31:06
举报
文章被收录于专栏:Zaqdt_ACMZaqdt_ACMZaqdt_ACM

       整体的实现思路就是传入一个字符串以及需要的参数(可变参数),通过对%的处理来获取我们所需要的类型,从而实现格式化字符串的操作( ("Hello %s world", "nginx") -> "Hello nginx world"),主要是细节的处理,比如有无符号类型,以及16进制转换和保留小数等问题,需要仔细思考。

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>   // 可变参
#include <stdint.h>
#include <string.h>

#include "ngx_global.h"
#include "ngx_macro.h"
#include "ngx_func.h"

static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width);

// 用于调用主要的ngx_vslprintf()函数
u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...){
    va_list args;            // 定义可变参的变量
    u_char *p;

    va_start(args, fmt);     // 使args指向起始的参数
    p = ngx_vslprintf(buf, last, fmt, args);
    va_end(args);            // 释放args
    return p;
}

// 自定义的格式化输出
// buf:存储数据  last:最大的内存地址  fmt:可变参数开头(format)
u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args){

  u_char zero;       // 占位符号 0或者空格
  uintptr_t width, sign, hex, frac_width, scale, n;   // 需要用到的临时变量

  int64_t   i64;      // 保存%d
  uint64_t  ui64;     // 保存%ud
  u_char    *p;       // 保存%s
  double    f;        // 保存%f
  uint64_t  frac;     // 用于%.2f参数 保存保留的小数部分 %.2f 12.457 此时frac = 46

  // 处理fmt字符串
  while(*fmt && buf < last){
    if(*fmt == '%'){     // 表示该位置需要被替换

      // -----------------------初始化变量----------------------
      zero = (u_char)((*++fmt == '0') ? '0' : ' ');   // 判断%后面是否用0占位 如果不是0 就用空格占位 判断%5d这种类型
      width = 0;             // 用于判断需要占位的宽度(比如%5d 不够5位空格填补) 目前只对%d和%f有效
      sign = 1;              // 标记有无符号类型 如果是%u就设为0
      hex = 0;               // 是否以16进制显示 0:不是 1:是,且以小写字母显示 2:是,且以大写字母显示
      frac_width = 0;        // 小数点之后的数字 如%.2f frac_width=2
      i64 = 0;               // 保存一些数字
      ui64 = 0;              // 保存一些数字

      // 取出%后面的数字
      while(*fmt >= '0' && *fmt <= '9'){
        width = width * 10 + (*fmt++ - '0');
      }

      // 用来处理一些特殊的标记 u x X .
      for( ;; ){                // 由于%.后面会有数字  所以这里使用循环
        switch(*fmt){
          case 'u' :          // 说明是无符号类型
              sign = 0;
              fmt ++;
              continue;

          case 'X' :          // 以16进制显示 且以大写字母显示
              hex = 2;
              sign = 0;
              fmt ++;
              continue;

          case 'x' :          // 以16进制显示 且以小写字母显示
              hex = 1;
              sign = 0;
              fmt ++;
              continue;

          case '.' :          // 需要保留小数
              fmt ++;
              while(*fmt >= '0' && *fmt <= '9'){     // 取出需要保留的数字
                frac_width = frac_width * 10 + (*fmt++ - '0');
              }
              break;

          default :
              break;
        }    // end switch(*fmt)
        break;
      }      // end for(;;)

      // 判断剩下的一些类型 % d s p f
      switch(*fmt){
        case '%' :                  // %%表示只输出一个%
            *buf ++ = '%';
            fmt ++;
            continue;               // 重新从while(*fmt && buf < last)执行

        case 'd' :                  // 显示整型数据
            if(sign){               // 判断是否有符号
              i64 = (int64_t) va_arg(args, int);      // 使用va_arg按顺序将数据取出 第二个参数表示类型
            }
            else{                   // 无符号类型
              ui64 = (uint64_t) va_arg(args, u_int);
            }
            break;                  // 跳出当前的switch

        case 's' :                  // 显示字符串类型
            p = va_arg(args, u_char *);     // 将数据取出给p
            // 将p的内容赋值给buf
            while(*p && buf < last){
              *buf ++ = *p ++;
            }
            fmt ++;
            continue;               // 重新从while(*fmt && buf < last)执行

        case 'P' :                  // 显示一个pid_t类型
            i64 = (int64_t) va_arg(args, pid_t);
            sign = 1;
            break;

        case 'f' :                  // 显示浮点数类型
            f = va_arg(args, double);

            // 负数的处理
            if(f < 0){
              *buf++ = '-';         // 提前保存负号
              f = -f;               // 后面按正数处理
            }

            ui64 = (int64_t)f;
            frac = 0;

            // 处理保留小数
            if(frac_width){
              scale = 1;
              for(int i=0;i<frac_width;i++) scale *= 10;     // 缩放 这里可能会溢出

              frac = (uint64_t)((f - (double)ui64) * scale + 0.5); // 0.5用于判断保留小数后是否需要进位
              // 99.1 + 0.5 = 99.6 -> 99
              // 99.5 + 0.5 = 100.0 -> 100

              if(frac == scale){    // 判断是否要向整数部分进位
                // 当frac==scale时需要进位 也就是%2.f 12.999的情况
                ui64 ++;
                frac = 0;           // 进位后清空小数部分
              }
            }     // end if(frac_width)

            // 先将整数部分显示出来
            buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);    // 将数字保存在buf中

            // 指定了保留小数 此时将小数部分显示出来
            if(frac_width){
              if(buf < last) *buf ++ = '.';    // 显示小数点
              buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width);
            }
            fmt ++;
            continue;       // 重新从while(*fmt && buf < lase)执行


        // 这里可以安插一些其他格式符号的处理


        // 不是需要处理的特殊符号直接拷贝显示就可以
        default :
          *buf ++ = *fmt ++;
          continue;
      }     // end switch(*fmt)

      // 这里只有一些整型的数字可以走下来
      // 将有符号数字类型都转换为无符号类型类型 并显示到buf中
      if(sign){                // 有符号数
          if(i64 < 0){
            *buf ++ = '-';     // 提前显示负号
            ui64 = (uint64_t) -i64;    // 按无符号正数处理
          }
          else ui64 = (uint64_t) i64;
      }   // end if(sign)

      buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
      fmt ++;
    }    // end if(*fmt == '%')

    // 这里就是正常的字符 直接拷贝到buf里就可以了
    else{
      *buf ++ = *fmt ++;
    }
  }      // end while(*fmt && buf < last)
  return buf;
}

// 静态函数作用是当前函数只在当前文件中生效
// buf:存放数据  last:最大内存地址  ui64:无符号数字  zero:占位符号0或空格  hex:是否是16进制数  width: 显示的宽度 如果不足用zero补齐
static u_char * ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width){
  u_char    *p, temp[NGX_INT64_LEN + 1];
  size_t    len;
  uint32_t  ui32;

  static u_char    hex[] = "0123456789abcdef";
  static u_char    HEX[] = "0123456789ABCDEF";

  // 将p指向末尾
  p = temp + NGX_INT64_LEN;

  if(hexadecimal == 0){     // 不需要转换成16进制
    if(ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE){          // 最大32位无符号数
      ui32 = (uint32_t) ui64;    // 能够存下
      while(ui32){
        *--p = (ui32 % 10 + '0');     // 倒着保存
        ui32 /= 10;
      }
    }    // end if(ui64 <= NGX_MAX_UINT32_VALUE)
    else{
      while(ui64){
        *--p = (ui64 % 10 + '0');     // 倒着保存
        ui64 /= 10;
      }
    }
  }      // end if(hex == 0)
  else if(hexadecimal == 1){      // 需要转换成16进制 而且以小写字母显示
    do{
      // 0xf对应二进制的1111
      // ui64 & 0xf就相当于将末尾4位二进制数取出来 然后将其转换成uint32_t类型 通过下标找到对应的字符
      *--p = hex[(uint32_t)(ui64 & 0xf)];
    }
    while(ui64 >>= 4);    // 右移4位 相当于删除二进制末尾的四位
  }      // end else if(hex == 1)
  else{
    // 和hex == 1处理相同
    do{
      *--p = HEX[(uint32_t) (ui64 & 0xf)];
    }
    while(ui64 >>= 4);
  }      // end else

  // 通过末尾的位置减去当前p的位置就得到了这个数字的长度
  len = (temp + NGX_INT64_LEN) - p;

  if((buf + len) >= last){           // 存不下这个数字
    len = last - buf;                // 剩多少存多少
  }

  return ngx_cpymem(buf, p, len);
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档