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 int
outstring_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 int
outstring_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.c
size_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);
复制代码
⑥.返回结果
返回当前已输出字符数
评论