printf 函数实现的深入剖析[转载]
来源:互联网 发布:金隅悦城丽悦园网络 编辑:程序博客网 时间:2024/05/19 16:37
研究printf的实现,首先来看看printf函数的函数体!
int printf(const char *fmt, ...) { int i; char buf[256]; va_list arg = (va_list)((char*)(&fmt) + 4); i = vsprintf(buf, fmt, arg); write(buf, i); return i; }
代码位置:D:/~/funny/kernel/printf.c
在形参列表里有这么一个token:...
这个是可变形参的一种写法。
当传递参数的个数不确定时,就可以用这种方式来表示。
很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。
先来看printf函数的内容:
这句: va_list arg = (va_list)((char*)(&fmt) + 4);
va_list的定义: typedef char *va_list
这说明它是一个字符指针。其中的: (char*)(&fmt) + 4) 表示的是...中的第一个参数。
如果不懂,我再慢慢的解释:
C语言中,参数压栈的方向是从右往左。 也就是说,当调用printf函数的适合,先是最右边的参数入栈。
fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。 fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。
换句话说: 你sizeof(p) (p是一个指针,假设p=&i,i为任何类型的变量都可以)得到的都是一个固定的值。(32位计算机中都是得到的4)
当然,我还要补充的一点是:栈是从高地址向低地址方向增长的!
ok!
现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。
下面我们来看看下一句:
i = vsprintf(buf, fmt, arg);
让我们来看看vsprintf(buf, fmt, arg)是什么函数。
int vsprintf(char *buf, const char *fmt, va_list args){ char* p; char tmp[256]; va_list p_next_arg = args; for (p = buf; *fmt; fmt++) { if (*fmt != '%') { *p++ = *fmt; continue; } fmt++; switch (*fmt) { case 'x': itoa(tmp, *((int*)p_next_arg)); strcpy(p, tmp); p_next_arg += 4; p += strlen(tmp); break; case 's': break; default: break; } } return (p - buf);}
我们还是先不看看它的具体内容。
想想printf要做什么吧?
它接受一个格式化的命令,并把指定的匹配的参数格式化输出。ok,看看i = vsprintf(buf, fmt, arg);
vsprintf返回的是一个长度,我想你已经猜到了:是的,返回的是要打印出来的字符串的长度
其实看看printf中后面的一句:write(buf, i);你也该猜出来了。
write,顾名思义:写操作,把buf中的i个元素的值写到终端。
所以说:vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
我代码中的vsprintf只实现了对16进制的格式化。
你只要明白vsprintf的功能是什么,就会很容易弄懂上面的代码。
下面的write(buf, i);的实现就有点复杂了
如果你是os,一个用户程序需要你打印一些数据。很显然:打印的最底层操作肯定和硬件有关。 所以你就必须得对程序的权限进行一些限制!
让我们假设个情景:
一个应用程序对你说:os先生,我需要把存在buf中的i个数据打印出来,可以帮我么?
os说:好的,咱俩谁跟谁,没问题啦!把buf给我吧。
然后,os就把buf拿过来。交给自己的小弟(和硬件操作的函数)来完成。
只好通知这个应用程序:兄弟,你的事我办的妥妥当当!(os果然大大的狡猾 ^_^)
这样 应用程序就不会取得一些超级权限,防止它做一些违法的事。(安全啊安全)
让我们追踪下write吧:
write: mov eax, _NR_write mov ebx, [esp + 4] mov ecx, [esp + 8] int INT_VECTOR_SYS_CALL位置:d:~/kernel/syscall.asm
这里是给几个寄存器传递了几个参数,然后一个int结束。
想想我们汇编里面学的,比如返回到dos状态:
我们这样用的
mov ax,4c00h int 21h为什么用后面的int 21h呢?
这是为了告诉编译器:号外,号外,我要按照给你的方式(传递的各个寄存器的值)变形了。
编译器一查表:哦,你是要变成这个样子啊。no problem!
其实这么说并不严紧,如果你看了一些关于保护模式编程的书,你就会知道,这样的int表示要调用中断门了。通过中断门,来实现特定的系统服务。
我们可以找到INT_VECTOR_SYS_CALL的实现:
init_idt_desc(INT_VECTOR_SYS_CALL, DA_386IGate, sys_call, PRIVILEGE_USER);
位置:d:~/kernel/protect.c
好了,再来看看sys_call的实现:
sys_call: call save push dword [p_proc_ready] sti push ecx push ebx call [sys_call_table + eax * 4] add esp, 4 * 3 mov [esi + EAXREG - P_STACKBASE], eax cli ret
位置:~/kernel/kernel.asm
一个call save,是为了保存中断前进程的状态。
我只在乎我所在乎的东西。sys_call实现很麻烦,我们不妨不分析funny os这个操作系统了。
先假设这个sys_call就一单纯的小女孩。她只有实现一个功能:显示格式化了的字符串。
这样,如果只是理解printf的实现的话,我们完全可以这样写sys_call:
sys_call: ;ecx中是要打印出的元素个数 ;ebx中的是要打印的buf字符数组中的第一个元素 ;这个函数的功能就是不断的打印出字符,直到遇到:'\0' ;[gs:edi]对应的是0x80000h:0采用直接写显存的方法显示字符串 xor si,si mov ah,0Fh mov al,[ebx+si] cmp al,'\0' je .end mov [gs:edi],ax inc si loop: sys_call .end: retok!就这么简单!
恭喜你,重要弄明白了printf的最最底层的实现!
如果你有机会看linux的源代码的话,你会发现,其实它的实现也是这种思路。 freedos的实现也是这样。
比如在linux里,printf是这样表示的:
static int printf(const char *fmt, ...) { va_list args; int i; va_start(args, fmt); write(1,printbuf,i=vsprintf(printbuf, fmt, args)); va_end(args); return i; }
va_start、va_end 这两个函数在我的blog里有解释,这里就不多说了 。
它里面的vsprintf和我们的vsprintf是一样的功能。
不过它的write和我们的不同,它还有个参数:1
这里我可以告诉你:1表示的是tty所对应的一个文件句柄。
在linux里,所有设备都是被当作文件来看待的。你只需要知道这个1就是表示往当前显示器里写入数据
在freedos里面,printf是这样的:
int VA_CDECL printf(const char *fmt, ...) { va_list arg; va_start(arg, fmt); charp = 0; do_printf(fmt, arg); return 0; }
看起来似乎是do_printf实现了格式化和输出。
STATIC void do_printf(CONST BYTE * fmt, va_list arg) { int base; BYTE s[11], FAR * p; int size; unsigned char flags; for (;*fmt != '\0'; fmt++) { if (*fmt != '%') { handle_char(*fmt); continue; } fmt++; flags = RIGHT; if (*fmt == '-') { flags = LEFT; fmt++; } if (*fmt == '0') { flags |= ZEROSFILL; fmt++; } size = 0; while (1) { unsigned c = (unsigned char)(*fmt - '0'); if (c > 9) break; fmt++; size = size * 10 + c; } if (*fmt == 'l') { flags |= LONGARG; fmt++; } switch (*fmt) { case '\0': va_end(arg); return; case 'c': handle_char(va_arg(arg, int)); continue; case 'p': { UWORD w0 = va_arg(arg, unsigned); char *tmp = charp; sprintf(s, "%04x:%04x", va_arg(arg, unsigned), w0); p = s; charp = tmp; break; } case 's': p = va_arg(arg, char *); break; case 'F': fmt++; /* we assume %Fs here */ case 'S': p = va_arg(arg, char FAR *); break; case 'i': case 'd': base = -10; goto lprt; case 'o': base = 8; goto lprt; case 'u': base = 10; goto lprt; case 'X': case 'x': base = 16; lprt: { long currentArg; if (flags & LONGARG) currentArg = va_arg(arg, long); else { currentArg = va_arg(arg, int); if (base >= 0) currentArg = (long)(unsigned)currentArg; } ltob(currentArg, s, base); p = s; } break; default: handle_char('?'); handle_char(*fmt); continue; } { size_t i = 0; while(p[i]) i++; size -= i; } if (flags & RIGHT) { int ch = ' '; if (flags & ZEROSFILL) ch = '0'; for (; size > 0; size--) handle_char(ch); } for (; *p != '\0'; p++) handle_char(*p); for (; size > 0; size--) handle_char(' '); } va_end(arg);}
这个就是比较完整的格式化函数里面多次调用一个函数:handle_char
来看看它的定义:
STATIC VOID handle_char(COUNT c) { if (charp == 0) put_console(c); else *charp++ = c; }
里面又调用了put_console
显然,从函数名就可以看出来:它是用来显示的
void put_console(int c) { if (buff_offset >= MAX_BUFSIZE) { buff_offset = 0; printf("Printf buffer overflow!\n"); } if (c == '\n') { buff[buff_offset] = 0; buff_offset = 0; #ifdef __TURBOC__ _ES = FP_SEG(buff); _DX = FP_OFF(buff); _AX = 0x13; __int__(0xe6); #elif defined(I86) asm { push ds; pop es; mov dx, offset buff; mov ax, 0x13; int 0xe6; } #endif } else { buff[buff_offset] = c; buff_offset++; } }
注意:这里用递规调用了printf,不过这次没有格式化,所以不会出现死循环。
好了,现在你该更清楚的知道:printf的实现了
现在再说另一个问题:
无论如何printf()函数都不能确定参数...究竟在什么地方结束,也就是说,它不知道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数format后面地址的内容。这样就存在一个可能的缓冲区溢出问题。。。
- printf 函数实现的深入剖析[转载]
- printf函数实现的深入剖析
- printf函数的实现深入剖析
- printf函数实现的深入剖析
- printf函数实现的深入剖析
- printf函数实现的深入剖析
- printf 函数实现的深入剖析
- 实现自己的printf函数(转载)
- 可变参数列表的剖析以及printf函数的实现
- 深入剖析变长参数函数的实现
- 深入剖析变长参数函数的实现
- 深入剖析printf
- 从printf谈可变参数函数的实现[转载]
- 深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
- 深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
- 深入剖析printf函数 合集pdf文件
- printf 函数的实现 *
- printf函数的实现
- hive管理之命令行方式CLI
- maven 构建项目时 java路径下配置属性文件相关
- 求职准备428
- MyBatis中Mapper接口映射到数据库原理分析
- android studio编译速度过慢的解决办法。
- printf 函数实现的深入剖析[转载]
- JavaEE之Struts2获取表单数据
- Linux 守护进程的原理与实现
- squid
- TCP/UDP端口列表
- 自力更生打造自己的个人网站,开袋即食
- 关闭迅雷首页自动播放视频
- c++作业6
- 读书笔记《Effective C++》条款24:若所有参数皆需类型转换,请为此采用non-member函数