Q: 已经有write系统调用, 为何还需要printf?
A: write系统调用作为操作系统API,为用户层提供写文件最基本的实现.
printf作为libc的基本函数之一, 为c语言级别标准, 可以让用户层抛开操作系统write系统调用的差异,
提供一套带有缓冲机制的输出功能. 换句话说, c++, java等语言同样有类似的"printf", 最终一定会调用write系统调用.
需要在标准输出读写时, 我们可以不调用printf, 但write是一定会使用,我们还可以在write基础上再封装
一套新的"printf"实作, 作为自定义的printf.
Q: 给一个简单的printf实作实例.
A: 对于标准C语言, printf不像write那样,传递的参数比较单纯, printf需要处理不同的格式串, 缓冲区类型.
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
char *itoa(int i, char *buf)
{
int j = 0;
int k;
do {
k = i % 10;
buf[j++] = k + '0';
i /= 10;
} while(i);
buf[j] = '\0';
for (k = 0; k < j / 2; ++k) {
char temp = buf[k];
buf[k] = buf[j - 1 - k];
buf[j - 1 - k] = temp;
}
return buf;
}
int simple_printf(char *fmt, ...)
{
static char buf[128];
va_list ap;
char *p;
int val, i = 0;
char c;
ssize_t cnt = 0, total_cnt = 0;
char str[16];
memset(buf, 0, 128);
va_start(ap, fmt);
for (p = fmt; *p; p++) {
if (*p != '%') {
buf[i++] = *p; // add characters to output buffer
++cnt;
if (*p == '\n') { // new line, output all chars
cnt = write(0, buf, cnt);
}
continue;
}
switch (*++p) {
case 'd':
val = va_arg(ap, int);
itoa(val, str);
strcat(buf + i, str);
cnt += strlen(str);
i += strlen(str);
break;
case 'c':
val = va_arg(ap, int);
c = (char)val;
buf[i++] = c; // add characters to output buffer
cnt++;
break;
default:
buf[i++] = *p; // add characters to output buffer
cnt++;
break;
}
}
va_end(ap);
return cnt;
}
int main(int argc, char *argv[])
{
int ret;
ret = simple_printf("hello%d\n", 1);
simple_printf("%d\n", ret);
return 0;
}
上面的代码简单的展示了基本的printf实现流程.
1 缓冲区;
2 行缓冲;
3 格式%d或者%c解析;
4 调用write系统调用完成实际输出.
当然,细心的读者可以看到这个实现依然有不少异常处理/多线程问题没有处理, 有兴趣可以修正它!
附注: Apple libc源代码: https://opensource.apple.com/source/Libc/Libc-1272.250.1/
对于write系统调用内部的实现: write系统调用的实现
作者: 陈曦
环境: MacOS 10.14.5
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
转载请注明出处