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

  printf("Hello World!\n");

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

今天的旅行从这里开始:

#include <stdio.h>

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

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

 int __cdecl printf (
const char *format,
...
)
/*
* stdout 'PRINT', 'F'ormatted
*/
{
va_list arglist;
int buffing;
int retval; _VALIDATE_RETURN( (format != NULL), EINVAL, -); va_start(arglist, format); _lock_str2(, stdout);
__try {
buffing = _stbuf(stdout); retval = _output_l(stdout,format,NULL,arglist); _ftbuf(buffing, stdout); }
__finally {
_unlock_str2(, stdout);
} return(retval);
}

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

  

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

This is a complex process.  -_-b

  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么?

  

你真的懂printf么?的更多相关文章

  1. [C#] C# 知识回顾 - 你真的懂异常(Exception)吗?

    你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...

  2. 【转】was mutated while being enumerated 你是不是以为你真的懂For...in... ??

    原文网址:http://www.jianshu.com/p/ad80d9443a92 支持原创,如需转载, 请注明出处你是不是以为你真的懂For...in... ??哈哈哈哈, 我也碰到了这个报错 . ...

  3. javascript的语法作用域你真的懂了吗

    原文:javascript的语法作用域你真的懂了吗 有段时间没有更新了,思绪一下子有点转不过来.正应了一句古话“一天不读书,无人看得出:一周不读书,开始会爆粗:一月不读书,智商输给猪.”.再加上周五晚 ...

  4. 你真的懂ajax吗?

    前言 总括: 本文讲解了ajax的历史,工作原理以及优缺点,对XMLHttpRequest对象进行了详细的讲解,并使用原生js实现了一个ajax对象以方便日常开始使用. damonare的ajax库: ...

  5. “三次握手,四次挥手”你真的懂吗?TCP

    “三次握手,四次挥手”你真的懂吗?  mp.weixin.qq.com 来源:码农桃花源 解读:“拼多多”被薅的问题出在哪儿?损失将如何买单? 之前有推过一篇不错的干货<TCP之三次握手四次挥手 ...

  6. 你真的懂 ajax 吗?

    前言 总括: 本文讲解了ajax的历史,工作原理以及优缺点,对XMLHttpRequest对象进行了详细的讲解,并使用原生js实现了一个ajax对象以方便日常开始使用. damonare的ajax库: ...

  7. 【转】先说IEnumerable,我们每天用的foreach你真的懂它吗?

    [转]先说IEnumerable,我们每天用的foreach你真的懂它吗? 我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么Linq ...

  8. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  9. C# 知识回顾 - 你真的懂异常(Exception)吗?

    你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...

随机推荐

  1. Etag & If-None-Match 专题

    一.概述 缓存通俗点讲,就是将已经得到的‘东东’存放在一个相对于自己而言,尽可能近的地方,以便下次需要时,不会再二笔地跑到起始点(很远的地方)去获取,而是就近解决,从而缩短时间和节约金钱(坐车要钱嘛) ...

  2. sqlserver中获取最后一个字符所在的位置

    CHARINDEX('字符',reverse(字段名称)) 这个意思就是将字段进行反转,就是从后往前取,这样就能够获取一个字符最后所在的位置

  3. 在服务器中无法自宿主启动一个webapi项目,引出查看系统端口是否被占用

    在windows server下启动一个webapi的.net core项目报错 如下图: 让我以为服务器还需要别的配置.后来发现原来是线程被占用了. 在windows下查看某线程是否被占用的命令如下 ...

  4. TemplatePart用法说明

    原文:TemplatePart用法说明 TemplatePart(Name="PART_Decrease", Type=typeof(RepeatButton)) 一直没明白这是干 ...

  5. VC++中的C运行时库浅析(控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)

    1.概论 运行时库是程序在运行时所需要的库文件,通常运行时库是以LIB或DLL形式提供的.C运行时库诞生于20世纪70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于 ...

  6. .net core响应缓存

    按照官网资料操作无效,这里使用https://github.com/speige/AspNetCore.ResponseCaching.Extensions的扩展包 安装AspNetCore.Resp ...

  7. C++ Boost 学习资源列表

    文档书籍下载 Boost Documentation Boost代码下载       优秀网站导航 Boost官方网站 Boost中文站 Boost Consulting     专题资源报告 Lin ...

  8. NET实现RSA AES DES 字符串 加密解密以及SHA1 MD5加密

    本文列举了    数据加密算法(Data Encryption Algorithm,DEA) 密码学中的高级加密标准(Advanced EncryptionStandard,AES)RSA公钥加密算法 ...

  9. 文字滚屏控件(SliderPanel)

    http://www.delphifans.com/infoview/Article_629.html 日期:2005年9月6日 作者:arhaha {==================== 满天星 ...

  10. 虚拟机安装的kali无法识别本机物联网卡

    http://blog.csdn.net/sb985/article/details/76427624