定义在 src\core\ngx_string.c
u_char *
ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args)
{
u_char *p, zero;
int d;
double f;
size_t slen;
int64_t i64;
uint64_t ui64, frac;
ngx_msec_t ms;
ngx_uint_t width, sign, hex, max_width, frac_width, scale, n;
ngx_str_t *v;
ngx_variable_value_t *vv;
while (*fmt && buf < last) {
/*
* "buf < last" means that we could copy at least one character:
* the plain character, "%%", "%c", and minus without the checking
*/
if (*fmt == '%') {
i64 = 0;
ui64 = 0;
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
width = 0;
sign = 1;
hex = 0;
max_width = 0;
frac_width = 0;
slen = (size_t) -1;
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
for ( ;; ) {
switch (*fmt) {
case 'u':
sign = 0;
fmt++;
continue;
case 'm':
max_width = 1;
fmt++;
continue;
case 'X':
hex = 2;
sign = 0;
fmt++;
continue;
case 'x':
hex = 1;
sign = 0;
fmt++;
continue;
case '.':
fmt++;
while (*fmt >= '0' && *fmt <= '9') {
frac_width = frac_width * 10 + (*fmt++ - '0');
}
break;
case '*':
slen = va_arg(args, size_t);
fmt++;
continue;
default:
break;
}
break;
}
switch (*fmt) {
case 'V':
v = va_arg(args, ngx_str_t *);
buf = ngx_sprintf_str(buf, last, v->data, v->len, hex);
fmt++;
continue;
case 'v':
vv = va_arg(args, ngx_variable_value_t *);
buf = ngx_sprintf_str(buf, last, vv->data, vv->len, hex);
fmt++;
continue;
case 's':
p = va_arg(args, u_char *);
buf = ngx_sprintf_str(buf, last, p, slen, hex);
fmt++;
continue;
case 'O':
i64 = (int64_t) va_arg(args, off_t);
sign = 1;
break;
case 'P':
i64 = (int64_t) va_arg(args, ngx_pid_t);
sign = 1;
break;
case 'T':
i64 = (int64_t) va_arg(args, time_t);
sign = 1;
break;
case 'M':
ms = (ngx_msec_t) va_arg(args, ngx_msec_t);
if ((ngx_msec_int_t) ms == -1) {
sign = 1;
i64 = -1;
} else {
sign = 0;
ui64 = (uint64_t) ms;
}
break;
case 'z':
if (sign) {
i64 = (int64_t) va_arg(args, ssize_t);
} else {
ui64 = (uint64_t) va_arg(args, size_t);
}
break;
case 'i':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_uint_t);
}
if (max_width) {
width = NGX_INT_T_LEN;
}
break;
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
case 'l':
if (sign) {
i64 = (int64_t) va_arg(args, long);
} else {
ui64 = (uint64_t) va_arg(args, u_long);
}
break;
case 'D':
if (sign) {
i64 = (int64_t) va_arg(args, int32_t);
} else {
ui64 = (uint64_t) va_arg(args, uint32_t);
}
break;
case 'L':
if (sign) {
i64 = va_arg(args, int64_t);
} else {
ui64 = va_arg(args, uint64_t);
}
break;
case 'A':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_atomic_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_atomic_uint_t);
}
if (max_width) {
width = NGX_ATOMIC_T_LEN;
}
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 (n = frac_width; n; n--) {
scale *= 10;
}
frac = (uint64_t) ((f - (double) ui64) * scale + 0.5);
if (frac == scale) {
ui64++;
frac = 0;
}
}
buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);
if (frac_width) {
if (buf < last) {
*buf++ = '.';
}
buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width);
}
fmt++;
continue;
#if !(NGX_WIN32)
case 'r':
i64 = (int64_t) va_arg(args, rlim_t);
sign = 1;
break;
#endif
case 'p':
ui64 = (uintptr_t) va_arg(args, void *);
hex = 2;
sign = 0;
zero = '0';
width = 2 * sizeof(void *);
break;
case 'c':
d = va_arg(args, int);
*buf++ = (u_char) (d & 0xff);
fmt++;
continue;
case 'Z':
*buf++ = '\0';
fmt++;
continue;
case 'N':
#if (NGX_WIN32)
*buf++ = CR;
if (buf < last) {
*buf++ = LF;
}
#else
*buf++ = LF;
#endif
fmt++;
continue;
case '%':
*buf++ = '%';
fmt++;
continue;
default:
*buf++ = *fmt++;
continue;
}
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
fmt++;
} else {
*buf++ = *fmt++;
}
}
return buf;
}
while (*fmt && buf < last) {
逐个解析格式化字符串(
fmt
)中的字符,并根据格式说明符将参数数据写入目标缓冲区(buf
)
*fmt
:检查格式化字符串是否尚未结束(\0
)。
buf < last
:确保目标缓冲区仍有空间(last
指向缓冲区末尾的下一个位置,因此buf
必须严格小于last
才允许写入)
if (*fmt == '%') {
当格式化字符串(
fmt
)遇到%
字符时,表示进入一个格式说明符的解析过程,该过程负责提取参数、应用格式规则(如宽度、精度、符号等),并将数据安全写入缓冲区。
此时
*fmt=%
进入 if
i64 = 0;
ui64 = 0;
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
width = 0;
sign = 1;
hex = 0;
max_width = 0;
frac_width = 0;
slen = (size_t) -1;
初始化一些关键变量
i64 = 0;
初始化一个 64 位有符号整数变量 i64,用于存储后续可能的有符号整数。
ui64 = 0;
初始化一个 64 位无符号整数变量 ui64,用于存储后续可能的无符号整数。
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
检查格式化字符串的下一个字符是否是 '0'。
如果是 '0',则将 zero 设置为 '0',表示后续的数字格式化会用零填充;
否则设置为 ' ',表示用空格填充。
fmt 指针会向前移动一位。
此时
zero= (空格)
width=0;
初始化宽度变量 width,用于存储格式化指令中指定的宽度
sign = 1;
初始化符号标志 sign,默认值为 1,表示后续的数字是有符的。如果后续遇到 u 或其他无符号修饰符,会将 sign 设置为 0。
hex = 0;
初始化十六进制标志 hex,默认值为 0,表示后续的数字不是十六进制格式。如果后续遇到 x 或 X,会将 hex 设置为 1 或 2。
max_width = 0;
初始化最大宽度变量 max_width,用于存储格式化指令中可能的最大宽度限制。
frac_width = 0;
初始化小数部分宽度变量 frac_width,用于存储浮点数格式化指令中指定的小数部分宽度(例如 %.2f 中的 2)。
slen = (size_t) -1;
初始化字符串长度变量 slen,默认值为 (size_t) -1,表示字符串长度未指定。如果后续遇到 *,会从 va_list args 中取出实际的长度值。
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
解析格式说明符中的宽度字段(例如`%10d`中的`10`),并将其转换为整数值存储在`width`变量中。
width
用于控制后续格式化输出的宽度
此时
*fmt=s
条件不成立
for ( ;; ) {
switch (*fmt) {
循环,解析修饰符
此时
*fmt=s
case 'u':
sign = 0;
fmt++;
continue;
case 'm':
max_width = 1;
fmt++;
continue;
case 'X':
hex = 2;
sign = 0;
fmt++;
continue;
case 'x':
hex = 1;
sign = 0;
fmt++;
continue;
case '.':
fmt++;
while (*fmt >= '0' && *fmt <= '9') {
frac_width = frac_width * 10 + (*fmt++ - '0');
}
break;
case '*':
slen = va_arg(args, size_t);
fmt++;
continue;
default:
break;
}
此时进入 default 分支
break;
跳出循环
switch (*fmt) {
处理数据类型修饰符
此时
*fmt=s
接下来 进入这个分支
case 's':
p = va_arg(args, u_char *);
buf = ngx_sprintf_str(buf, last, p, slen, hex);
fmt++;
continue;
处理格式化字符串中的 s 修饰符。
s 表示需要插入一个普通的字符串,这个字符串的地址存储在参数列表 args 中。
代码会从 args 中取出这个字符串的地址,并将其内容格式化到输出缓冲区 buf 中。
p = va_arg(args, u_char *);
这行代码从参数列表 args 中取出一个 u_char * 类型的指针,并将其存储到变量 p 中。
buf = ngx_sprintf_str(buf, last, p, slen, hex);
这行代码调用 ngx_sprintf_str 函数,将字符串 p 格式化到输出缓冲区 buf 中
fmt++;
指针后移
continue;
进入下一次循环,处理下一个字符
while (*fmt && buf < last) {
此时
*fmt=,
else {
*buf++ = *fmt++;
}
直接复制
然后进入下一次循环
while (*fmt && buf < last) {
此时
*fmt= (空格)
直接复制
然后进入下一次循环
*fmt=%
if (*fmt == '%') {
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
此时
zero=0
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
解析宽度,转换为整数
此时
width=2
for ( ;; ) {
switch (*fmt) {
此时
*fmt=d
default:
break;
进入这个分支
break;
结束循环
switch (*fmt) {
此时
*fmt=d
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
进入这个 分支
此时
sign=1(默认值)
取下一个参数,参数类型是 int
转换为 int64_t 赋值给 i64
此时
i64=2
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
检查是否需要处理数值的符号位
sign=1
:表示当前处理的数值是有符号类型
sign=0
:表示数值是无符号类型,直接跳过符号处理。此时条件成立
判断数值是否为负数
此时条件不成立
数值非负,直接将其转换为无符号整数
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
作用:调用函数将数值ui64格式化为字符串,并写入缓冲区。
参数解析:
buf:当前缓冲区写入位置(函数返回后更新到新位置)。
last:缓冲区末尾地址(防止溢出)。
ui64:待格式化的无符号数值
zero:填充字符('0'或' '),用于宽度不足时补位。
hex:进制模式(0=十进制,1=小写十六进制,2=大写十六进制)。
width:最小输出宽度(若实际位数不足,左侧填充zero字符)。此时
ui64=2
zero=0
hex=0
width=2
fmt++;
指针后移,处理下一个字符
进入下一次循环
while (*fmt && buf < last) {
此时
*fmt= (空格)
} else {
*buf++ = *fmt++;
}
直接 复制
进入下一次循环
while (*fmt && buf < last) {
*fmt=%
if (*fmt == '%') {
条件成立
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
此时
zero= (空格)
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
*fmt=s
条件不成立
for ( ;; ) {
switch (*fmt) {
*fmt=s
进入这个分支
default:
break;
然后
break;
结束 for 循环
switch (*fmt) {
*fmt=s
所以进入这个分支
case 's':
p = va_arg(args, u_char *);
buf = ngx_sprintf_str(buf, last, p, slen, hex);
fmt++;
continue;
取下一个参数,类型是 u_char *
把 p 指向的字符串 格式化到 buf
然后 指针后移处理下一个字符
下一次循环
while (*fmt && buf < last) {
*fmt= (空格)
} else {
*buf++ = *fmt++;
}
直接复制
下一次循环
while (*fmt && buf < last) {
此时
*fmt=%
if (*fmt == '%') {
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
此时
zero= (空格)
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
*fmt=4
width=4
下一个字符
*fmt=d
for ( ;; ) {
switch (*fmt) {
*fmt=d
进入这个分支
default:
break;
break;
switch (*fmt) {
此时
*fmt=d
进入这个分支
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
sign=1 是默认值
取下一个参数,类型为 int
i64=2025
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
进入 else
转换为
uint64_t
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
ui64 转换为 字符串 到 buf 指向的缓冲区
ui64=2025
zero= (空格)
hex=0
width=4
fmt++;
指针后移,处理下一个字符
下一次循环
while (*fmt && buf < last) {
*fmt= (空格)
} else {
*buf++ = *fmt++;
}
直接复制
下一次循环
while (*fmt && buf < last) {
*fmt=%
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
zero=0
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + (*fmt++ - '0');
}
width=2
for ( ;; ) {
switch (*fmt) {
*fmt=d
进入这个分支
default:
break;
}
break;
结束 for 循环
switch (*fmt) {
*fmt=d
进入这个分支
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
sign=1
取下一个参数,类型是 int
i64=10
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
整数转换为字符
ui64=10
zero=0
hex=0
width=2
fmt++;
指向下一个要处理的字符
后面的逻辑 同上
return buf;
最后返回 最后有效字符的下一个字节位置