7.6实现字符串的格式化输出
log_printf("Version: %s", OS_VERSION);
后面是可变参数,怎么接收这个可变参数?
C语言stdarg.h库的学习
stdarg.h简介
stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。
可变参数的函数通在参数列表的末尾是使用省略号(,…)定义的
va_list
这是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型。
序号 | 宏&描述 |
---|---|
1 | void va_start(va_list ap, last_arg) 这个宏初始化 ap 变量,它与 va_arg 和 va_end 宏是一起使用的。last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数。 |
2 | type va_arg(va_list ap, type) 这个宏检索函数参数列表中类型为 type 的下一个参数。 |
3 | void va_end(va_list ap) 这个宏允许使用了 va_start 宏的带有可变参数的函数返回。如果在从函数返回之前没有调用 va_end,则结果为未定义。 |
void va_start(va_list ap, xxx v) /* 其中的 xxx 为任意类型变量 */
{
ap = (va_list)&v + sizeof(v); //第一个可选参数地址
}
xxx va_arg(va_list ap, xxx t) /* 其中的 xxx 为任意类型变量 */
{
// (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
ap += sizeof(t); //下一个参数地址
return *((t *)(ap - sizeof(t))) //第一次调用返回的就是第一个参数的值
}
void va_end(va_list ap)
{
ap = (va_list)0; //强制转换,结尾v这个特殊的数组清零
}
== 这个库函数的底层也是利用了形参在函数栈中的位置(这是固定的,之前16位实模式向32位保护模式传信息也是靠这个方法来实现的),来取出形参==
如何判断输入的形参结束了,最后那个fmt字符串走到了\0,形参输出完毕
格式化字符串函数
将格式化后的完整字符串放到char数组中
void kernel_vsprintf(char * buffer, const char * fmt, va_list args) {
enum {NORMAL, READ_FMT} state = NORMAL;
char ch;
char * curr = buffer;
while ((ch = *fmt++)) {
switch (state) {
// 普通字符
case NORMAL:
if (ch == '%') {
state = READ_FMT;
} else {
*curr++ = ch;
}
break;
// 格式化控制字符,只支持部分
case READ_FMT:
if (ch == 's') {
const char * str = va_arg(args, char *);
int len = kernel_strlen(str);
while (len--) {
*curr++ = *str++;
}
}
state = NORMAL;
break;
}
}
}
这个函数设计蛮巧妙的
日志打印函数带格式化输出的改进
void log_printf(const char * fmt, ...) {
char str_buf[128];
va_list args;
kernel_memset(str_buf, '\0', sizeof(str_buf));
va_start(args, fmt);
kernel_vsprintf(str_buf, fmt, args);//将字符串格式化到一个缓冲区里面
va_end(args);
const char * p = str_buf;
while (*p != '\0') {
while ((inb(COM1_PORT + 5) & (1 << 6)) == 0); //看串行接口是不是处于忙的状态
outb(COM1_PORT, *p++);
}
outb(COM1_PORT, '\r'); //换行,改变了列号变为0
outb(COM1_PORT, '\n');//回车,改变了行号变为下一行行号
}