整体的实现思路就是传入一个字符串以及需要的参数(可变参数),通过对%的处理来获取我们所需要的类型,从而实现格式化字符串的操作( ("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);
}