如何让标准函数printf函数打印的打印内容输出像rt

C程序设计语言(完美中文版).txt
C程序设计语言(完美中文版)以及 计算机科学2011最新版。
[C程序设计语言]文字版。与扫描版排版一致,完整书签可用。
软件编程类参考书
C语言程序设计。。完整版
数据结构习题答案+耿国华主编.txt
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
刚刚上传了
在此可输入您对该资料的评论~
资料阅读排行
请选择举报的类型
赌博犯罪类
资料评价:
所需积分:0printf函数实现的深入剖析
printf函数实现的深入剖析
发布时间: 7:58:50
编辑:www.fx114.net
本篇文章主要介绍了"printf函数实现的深入剖析",主要涉及到printf函数实现的深入剖析方面的内容,对于printf函数实现的深入剖析感兴趣的同学可以参考一下。
/blogger/post_show.asp?BlogID=462085&PostID=8363874
 研究printf的实现,首先来看看printf函数的函数体
  int printf(const char *fmt, ...)
   char buf[256];
   va_list arg = (va_list)((char*)(&fmt) + 4);&
   i = vsprintf(buf, fmt, arg);
   write(buf, 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为任何类型的变量都可以)
  得到的都是一个固定的值。(我的计算机中都是得到的4)
  当然,我还要补充的一点是:栈是从高地址向低地址方向增长的。
  现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。
  下面我们来看看下一句:
   i = vsprintf(buf, fmt, arg);
  让我们来看看vsprintf(buf, fmt, arg)是什么函数。
  int vsprintf(char *buf, const char *fmt, va_list args)
   char*
   va_list
p_next_arg =
   for (p=*fmt++) {
   if (*fmt != '%') {
   *p++ = *
   fmt++;
   switch (*fmt) {
   case 'x':
   itoa(tmp, *((int*)p_next_arg));
   strcpy(p, tmp);
   p_next_arg += 4;
   p += strlen(tmp);
   case 's':
   default:
   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:
eax, _NR_write
ebx, [esp + 4]
ecx, [esp + 8]
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
  如果你不懂,没关系,你只需要知道一个int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数。(从上面的参数列表中也该能够猜出大概)
  好了,再来看看sys_call的实现:
  sys_call:
dword [p_proc_ready]
[sys_call_table + eax * 4]
esp, 4 * 3
[esi + EAXREG - P_STACKBASE], eax
  位置:~/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
   sys_call
  ok!就这么简单!
  恭喜你,重要弄明白了printf的最最底层的实现!
  如果你有机会看linux的源代码的话,你会发现,其实它的实现也是这种思路。
  freedos的实现也是这样
  比如在linux里,printf是这样表示的:
  static int printf(const char *fmt, ...)
   va_start(args, fmt);
   write(1,printbuf,i=vsprintf(printbuf, fmt, args));
   va_end(args);
   va_start&
   va_end 这两个函数在我的blog里有解释,这里就不多说了
  它里面的vsprintf和我们的vsprintf是一样的功能。
  不过它的write和我们的不同,它还有个参数:1
  这里我可以告诉你:1表示的是tty所对应的一个文件句柄。
  在linux里,所有设备都是被当作文件来看待的。你只需要知道这个1就是表示往当前显示器里写入数据
  在freedos里面,printf是这样的:
   int VA_CDECL printf(const char *fmt, ...)
   va_start(arg, fmt);
   charp = 0;
   do_printf(fmt, arg);
   return 0;
  看起来似乎是do_printf实现了格式化和输出。
  我们来看看do_printf的实现:
  STATIC void do_printf(CONST BYTE * fmt, va_list arg)
   BYTE s[11], FAR *
   for (;*fmt != '\0'; fmt++)
   if (*fmt != '%')
   handle_char(*fmt);
   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)
   fmt++;
   size = size * 10 +
   if (*fmt == 'l')
   flags |= LONGARG;
   fmt++;
   switch (*fmt)
   case '\0':
   va_end(arg);
   case 'c':
   handle_char(va_arg(arg, int));
   case 'p':
   UWORD w0 = va_arg(arg, unsigned);
   char *tmp =
   sprintf(s, &%04x:%04x&, va_arg(arg, unsigned), w0);
   charp =
   case 's':
   p = va_arg(arg, char *);
   case 'F':
   fmt++;
   /* we assume %Fs here */
   case 'S':
   p = va_arg(arg, char FAR *);
   case 'i':
   case 'd':
   base = -10;
   case 'o':
   base = 8;
   case 'u':
   base = 10;
   case 'X':
   case 'x':
   base = 16;
   lprt:
   long currentA
   if (flags & LONGARG)
   currentArg = va_arg(arg, long);
   currentArg = va_arg(arg, int);
   if (base &= 0)
   currentArg = (long)(unsigned)currentA
   ltob(currentArg, s, base);
   default:
   handle_char('?');
   handle_char(*fmt);
   size_t i = 0;
   while(p[i]) i++;
   size -=
   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);
   *charp++ =
  里面又调用了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)
   mov dx,
   mov ax, 0x13;
   int 0xe6;
  #endif
   buff[buff_offset] =
   buff_offset++;
  注意:这里用递规调用了printf,不过这次没有格式化,所以不会出现死循环。
  好了,现在你该更清楚的知道:printf的实现了
  现在再说另一个问题:
  无论如何printf()函数都不能确定参数...究竟在什么地方结束,也就是说,它不知
  道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数format后面地址
  的内容。
  这样就存在一个可能的缓冲区溢出问题。。。
  下次有时间再写^_^
  有任何问题可以和我联系讨论:
   --write by 落单的侯鸟
一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!
二、互相尊重,对自己的言论和行为负责。
本文标题:
本页链接:关于linux下printf函数输出不及时的解决
关于linux下printf函数输出不及时的解决
在linux系统下,printf函数是行缓冲式的输出,当printf遇到\n时,或者缓冲区满时,才会将缓冲区里的内容刷新到标准输出(stdout).
因此, printf("p"); 等语句的显示不能立刻显示在屏幕上,但是printf("p\n"); 可以.
为了解决这个问题可以通过:fflush(stdout);解决,即变成:
printf("p");
fflush(stdout);
问题可以得到解决.
相对printf,使用fprintf的好处
printf函数的接口如下所示:
int printf( const char *
);fprintf函数的接口如下所示:
int fprintf( FILE *, const
从接口可以看出,使用printf函数会更方便些,少了一个函数参数。在通常情况下,的确是这样的。但在输出信息比较多,而且输出的信息有可能需要分类的化,使用fprintf的好处就体现出来了。
printf函数使用的输出设备是标准输出设备,即stdout。在使用fprintf函数时,我们可以在第一个参数使用stdout或stderr进行输出信息分类。虽然stdout和stderr在正常情况下都指向标准输出设备,但通常程序运行时的命令行参数,可以分别控制stdout和stderr指向不同的设备或文件。这样,就可以达到对输出信息进行分类控制了。如下面的代码段所示:
//一般输出信息
extern char *
extern char *
fprintf(stdout, "%s", info);
//特别关注的输出信息
fprintf(stderr, "%s", errinfo);
这样,在运行程序时,可以使用如下的命令行:
test 1&&1.txt
这样,一般输出信息可以存储到1.txt文件中,特别关注的输出信息可以存储到2.txt文件中。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。}

我要回帖

更多关于 printf输出格式 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信