cstdio 中的格式化输入输出函数
fprintf 函数的实现 vfprintf 中包含了相当多的宏定义和辅助函数,接下来我们一起来分析一下它们对应的源码实现。
函数逻辑分析---vfprintf
2.宏定义/辅助函数分析
(10).outchar(Ch)---输出字符 Ch
这里的核心逻辑是调用前面提到过的 PUTC 宏将字符 Ch 输出到对应的文件流 s 中,但是有以下几点需要注意:
对 Ch 的转换操作:const INT_T outc = (Ch),这个操作看上去没有什么作用,可以直接填入 PUTC 宏中,实际上这里有两个作用:
一个是保证给到 PUTC 的入参一定是合法的 int 型数据;
另一个是保证形如 outchar (*w++)这样的调用在宏展开时不会遇到符号优先级变化的问题。
这种情况*w++就会被直接展开,可能在后续的使用中导致出现符号异常(++运算符被解释为对后面的变量++),所以我们有两种解决方式,一个是保证这里传给 PUTC 的已经是一个计算出来的结果,一个是在 PUTC 中加括号保证有限级。在 Glibc 的宏的使用中我们会看到很多宏中添加的括号,也是为了保证这一点。
做了异常判断:PUTC (outc, s) == EOF 表示输出字符失败,done == INT_MAX 表示当前已经到达最大输出字符数,这种情况会失败;
如果输出成功,那么我们就将 done(计算当前输出字符数)加一。
outchar (*w++); #define outchar(Ch) \ do \ { \ const INT_T outc = (Ch); \ if (PUTC (outc, s) == EOF || done == INT_MAX) \ { \ done = -1; \ goto all_done; \ } \ ++done; \ } \ while (0)
复制代码
(11).outstring_func---输出字符串函数
函数逻辑比较清晰:
检查是否已经到达最大输出字符数量;
调用前面提到的 PUT 宏输出指定长度的字符串,如果返回值不等于指定长度,则认为函数调用失败,返回-1;
如果调用 PUT 宏成功,那么需要调用 done_add_func 函数计算当前 done 的值,并进行返回。
done = outstring_func (s, (const UCHAR_T *) buf, written, done); static inline intoutstring_func (FILE *s, const UCHAR_T *string, size_t length, int done){ assert ((size_t) done <= (size_t) INT_MAX); if ((size_t) PUT (s, string, length) != (size_t) (length)) return -1; return done_add_func (length, done);}
复制代码
(12).outstring---输出字符串
入参
String:buffer 地址
Len:输出字符串长度
实际上是 outstring_func 的宏封装,做了容错机制,当返回 done 小于 0 时,直接跳转到 all_done,这里有以下两点需要关注:
对 String 的重新赋值:const void *string_ = (String),原因与之前说到的类似,保证宏展开之后可以正常运算;
函数传参时对 Len 的加括号,这里我们通过例子可以看到,传参时,是可能传入一个待计算结果的,加括号,保证运算优先级。
/* Write the literal text before the first format. */ outstring ((const UCHAR_T *) format, lead_str_end - (const UCHAR_T *) format);
#define outstring(String, Len) \ do \ { \ const void *string_ = (String); \ done = outstring_func (s, string_, (Len), done); \ if (done < 0) \ goto all_done; \ } \ while (0)
复制代码
(13).outstring_converted_wide_string---输出宽字符函数
①.函数入口
注意:这里的源字符串使用了 OTHER_CHAR_T,还记得我们之前提到过,标准字符和宽字符互相把对方设置为自己的 OTHER_CHAR_T,所以这里我们以 OTHER_CHAR_T 为 wchar_t 来进行分析。
/* Write the string SRC to S. If PREC is non-negative, write at most PREC bytes. If LEFT is true, perform left justification. */static intoutstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec, int width, bool left, int done){
复制代码
②.变量准备
因为要处理宽字符,所以我们必须要申请一块 buffer 来处理转换后的数据,这块 buffer 的大小要满足__wcsrtombs 的需求,这里,我们使用了 256/sizeof(CHAR_T),应该是足够使用的,同时,我们我们使用 assert 判断我们申请的 buf 大小是否满足 Glibc 内部规定的最大宽字符长度。
/* Use a small buffer to combine processing of multiple characters. CONVERT_FROM_OTHER_STRING expects the buffer size in (wide) characters, and buf_length counts that. */ enum { buf_length = 256 / sizeof (CHAR_T) }; CHAR_T buf[buf_length]; _Static_assert (sizeof (buf) > MB_LEN_MAX, "buffer is large enough for a single multi-byte character"); # define CONVERT_FROM_OTHER_STRING __wcsrtombs
// glibc/include/limits.h/* Maximum length of any multibyte character in any locale. We define this value here since the gcc header does not define the correct value. */#define MB_LEN_MAX 16
复制代码
③.处理左边的对齐情况
如果对齐宽度大于 0 且没有指定左对齐,这时就需要先输出对齐字符:
首先计算上面的入参信息中一共要输出多少有效字符 total_written,
然后再计算并输出对齐字符。
/* Add the initial padding if needed. */ if (width > 0 && !left) { // 计算total_written长度 ... // 输出对齐字符 ... }
复制代码
计算 total_written 长度,这里的逻辑也比较清晰,主要是调用 CONVERT_FROM_OTHER_STRING 进行计算,流程如下:
申请局部变量 mbstate(用来进行字符转换),src_copy(源字符串指针的拷贝),total_written(计数总需要输出的字符数);
如果 prec 小于 0,说明不进行字符输出,但是我们仍要计算 total_written,注意,对应的__wcsrtombs 函数中有专门针对 dst 为空指针的情况,这里我们就不进行展开了。
如果 prec 非负,说明需要进行字符转换后进行计算:
注意:因为源字符不能够以 nul 结尾,所以这里我们需要手动对字符长度进行调整
src_copy 每次调用 CONVERT_FROM_OTHER_STRING 之后都会被移到未转换的第一个字节位置;
limit 表示需要转换输出的字节数;所以遍历条件就是 limit > 0 && src_copy != NULL。
在循环中我们每次最多转换 buf_length 个字节,赋值给 write_limit,如果 write_limit > limit,说明当前需要转换的字符数没有 buf_length 个字节那么多,我们就需要更新 write_limit 为剩余的字符数;
然后调用 CONVERT_FROM_OTHER_STRING 进行字符转换,如果返回值为-1,说明转换失败,返回-1;如果返回值为 0,说明本次没有字符需要转换,结束循环;否则就对 total_written 计数累加,对 limit 计数累减。
/* Make a first pass to find the output width, so that we can add the required padding. */ mbstate_t mbstate = { 0 }; const OTHER_CHAR_T *src_copy = src; size_t total_written; if (prec < 0) total_written = CONVERT_FROM_OTHER_STRING (NULL, &src_copy, 0, &mbstate); else { /* The source might not be null-terminated. Enforce the limit manually, based on the output length. */ total_written = 0; size_t limit = prec; while (limit > 0 && src_copy != NULL) { size_t write_limit = buf_length; if (write_limit > limit) write_limit = limit; size_t written = CONVERT_FROM_OTHER_STRING (buf, &src_copy, write_limit, &mbstate); if (written == (size_t) -1) return -1; if (written == 0) break; total_written += written; limit -= written; } } // glibc/wcsmbs/wcsrtombs.csize_t__wcsrtombs (char *dst, const wchar_t **src, size_t len, mbstate_t *ps){... size_t result; /* We have to handle DST == NULL special. */ if (dst == NULL) { .... /* Count the number of bytes. */ result += data.__outbuf - buf; ... }...return result;}
复制代码
输出对齐字符,主要是调用 pad_func 函数进行输出,关注以下几点:
只有上面一步计算的 total_written<width,即要输出的总字节数小于对齐宽度时才需要添加对齐字符;
对齐字符的数量为 width - total_written,默认对齐字符是 L_(' '),即空格。
/* Output initial padding. */ if (total_written < width){ done = pad_func (s, L_(' '), width - total_written, done); if (done < 0) return done;}
复制代码
④.字符转换与输出
这里的逻辑基本与计算 total_written 时基本一致,我们就不深入分析细节了。
注意,这里是调用 outstring_func 将转换出的 buf 中的数据输出到文件流对象中
/* Convert the input string, piece by piece. */ size_t total_written = 0; { mbstate_t mbstate = { 0 }; /* If prec is negative, remaining is not decremented, otherwise, it serves as the write limit. */ size_t remaining = -1; if (prec >= 0) remaining = prec; while (remaining > 0 && src != NULL) { size_t write_limit = buf_length; if (remaining < write_limit) write_limit = remaining; size_t written = CONVERT_FROM_OTHER_STRING (buf, &src, write_limit, &mbstate); if (written == (size_t) -1) return -1; if (written == 0) break; done = outstring_func (s, (const UCHAR_T *) buf, written, done); if (done < 0) return done; total_written += written; if (prec >= 0) remaining -= written; } }
复制代码
⑤.处理右边的对齐情况
这里针对的是左对齐的情况,需要调用 pad_func 输出 width - total_written 个空格字符。
/* Add final padding. */ if (width > 0 && left && total_written < width) return pad_func (s, L_(' '), width - total_written, done);
复制代码
⑥.返回结果
返回当前已输出字符数
评论