你真的懂printf么?

  自从你进入程序员的世界,就开始照着书本编写着各类helloworld,大笔一挥:程序员

  printf("Hello World!\n");架构

  因而控制台神奇地出现了一行字符串,计算机一句舒适的问候将多少年轻的骚年们引入了这个比58同城还神奇的世界......ide

今天的旅行从这里开始:函数

#include <stdio.h>spa

int main()
{
float a = 0.5;
printf("float a is %f\n",a);
return 0;
}指针

第一步:进入调试,咱们首先进入了printf.c中的代码:调试

 1 int __cdecl printf (
 2         const char *format,
 3         ...
 4         )
 5 /*
 6  * stdout 'PRINT', 'F'ormatted
 7  */
 8 {
 9     va_list arglist;
10     int buffing;
11     int retval;
12 
13     _VALIDATE_RETURN( (format != NULL), EINVAL, -1);
14 
15     va_start(arglist, format);
16 
17     _lock_str2(1, stdout);
18     __try {
19         buffing = _stbuf(stdout);
20 
21         retval = _output_l(stdout,format,NULL,arglist);
22 
23         _ftbuf(buffing, stdout);
24 
25     }
26     __finally {
27         _unlock_str2(1, stdout);
28     }
29 
30     return(retval);
31 }
View Code

  因而,咱们知道了printf中关键的问题就是传入多个参数,以及如何将信息解析并输出。而参数的传递主要就是由几个宏完成的:va_list、va_start、va_end,这三个宏的定义在stdarg.h中又被替换了,而真正的定义则是在vadefs.h中:code

  

  个人机器是x86架构,32位win7系统,因此走的是上面这个宏定义块。打开了这个文件,你就知道了printf是多么的体系架构相关,因此致使printf的移植工做至关困难,至少我见过的每一本讲到printf和stdarg.h的书都会来上相似下面的一句:orm

This is a complex process.  -_-bblog

  va_list其实就是char *,唬人用的;va_start(ap, fmt)定位第一个参数的地址,va_end(ap)将ap指针指向空,防止野指针问题。接下来咱们能够看到printf函数的实质就是try块中的三个函数,接着咱们跟进去看看到底怎么回事。

  

第二步:接着程序指针又跳到了_stfbuf.c文件里,去执行_stbuf(stdout)函数了。

  这个函数主要就是初始化缓冲区,为后面转化和打印作准备,缓冲区大小是4096个字节,若成功执行返回值为1。

 

第三步:接下来到文件output.c中的_output_l函数中:

  这个函数算是核心吧,首先会设置一大堆的标志值,好比浮点精度,先后缀添0的位数什么的,具体每一个是干什么的我也说不全。仍是来看下面这个过程吧,1068行的while循环就开始解析字符串了,此时format的值就是“float a is %f\n”,一个字符一个字符地取放入ch中,而后下面去检查类型,好比此时ch的值就是f,是一个普通的字符,因此状态state的值就设置为ST_NORMAL,去执行下面的switch语句。

  

  switch语句里,有不少状态分支,普通字符ST_NORMAL、百分号ST_PERCENT(这个对于printf来讲就是比较特殊的)、ST_FLAG(符号)等等。对于普通字符,直接就写到缓冲区里去了,每写一个字符,format指针就向后移一位。当解析到%号时,就执行ST_PERCENT的分支。

  

  

  以后读取后一个字符,发现是f则再找到ST_TYPE的分支,执行数据的提取工做。argptr就是指向浮点数a地址的指针。按double类型提取数据并保存到tmp里。

  

  

  

第四步:当数据提取出来后,执行转换函数:

  当数据保存到tmp中,就能够执行下列这个函数去将数字转换为字符串并存放到buf里了printf的关键就是如何解析(或者说提取)参数,最终将参数所有转换为字符串放到一个buffer里输出到屏幕上。浮点数转换后存放在char * 型的text.sz里,有兴趣能够深刻这个convert函数里看看怎么转换的~(对于浮点好像有个名叫DTOA函数,记不太清了)

  

  在这些工做完成后output_l函数就返回了,返回的是打印的字符数:20。(你能够数一下下面控制台的输出字符数,最后包括了换行符)

 

第五步:将字符串写到标准输出流:

  返回到printf.c中,执行_ftbuf函数,其中再进入flush函数,在执行到下面指针指向的位置时,控制台才真正将所要打印的字符串打印出来,咱们的任务这时才算完成了。

  

  

  好了,再来讲说我为何要写这篇关于printf的文章,是由于在最近的项目里,碰到了跨平台printf的实现问题,浮点数打印不出来,查了不少资料都没有什么相关的,网上众说纷纭。因而我就到stackoverflow上发了个问,可是倒是一件很不愉快的事情。看看截图吧,首先是我提的原问题:

  

  接下来就好玩了~:

  

  原帖地址:http://stackoverflow.com/questions/18746570/printf-cannot-print-float-double-correctly-on-the-screen

  我确实一开始忘记打参数a,b,c了,可是这不影响问题自己,很明显最初回答的人根本没有通过思考,说我连printf的参数怎么写都不知道就来问,更有甚者把发问规则给我贴了出来,叫我不要发垃圾问题,还说我乱归结缘由,我只能无奈呵呵了~。那么我想问的是:首先别人提的问题有你想象的那么二么?其次,若是别人真的二,按没写参数的代码区编译执行会出现所描述的现象么?在没有认真看别人问题和现象描述的状况下,秀下限的来了~ 而在我将问题修改事后(仅仅是加上了printf中的a、b、c而已),而且一一回复了留言后(我无一例外的以sorry开头),前面赶着来秀下限的几却个没有一个能回答上来,甚至连本身的见解都提不出(我能说什么呢?呵呵!)。

  那么他们为何回答不出来呢?由于只有真正作过嵌入式交叉开发的人才会知道这个问题的可能缘由,在移植的工做中常常会碰到printf的相关问题,不光是浮点数打印不出来。好吧,看看真正懂的人是怎么说的吧,有两个回答都是可能的缘由(原本还有人提的一个答案,被我屏蔽掉了,是来秀下限的~),群众的眼睛是雪亮的,从我在项目中调试的结果看,应该是下面第一个缘由,任务栈的字节对齐问题,致使浮点传参错误;第二个回答也是一种可能,但咱们在编译时确实没有加下面的宏。果断vote之~:

 

  

 

  

 

  在C Interfaces and Implementations里也讲解了一种printf的实现,对标准C库的printf有必定的改进,代码是要讲可重用性和健壮性的,有兴趣能够看看。

  好了,扪心自问一下,你真的懂printf么?

相关文章
相关标签/搜索