你真的懂printf么?
自从你进入程序员的世界,就开始照着书本编写着各种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么?的更多相关文章
- [C#] C# 知识回顾 - 你真的懂异常(Exception)吗?
你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...
- 【转】was mutated while being enumerated 你是不是以为你真的懂For...in... ??
原文网址:http://www.jianshu.com/p/ad80d9443a92 支持原创,如需转载, 请注明出处你是不是以为你真的懂For...in... ??哈哈哈哈, 我也碰到了这个报错 . ...
- javascript的语法作用域你真的懂了吗
原文:javascript的语法作用域你真的懂了吗 有段时间没有更新了,思绪一下子有点转不过来.正应了一句古话“一天不读书,无人看得出:一周不读书,开始会爆粗:一月不读书,智商输给猪.”.再加上周五晚 ...
- 你真的懂ajax吗?
前言 总括: 本文讲解了ajax的历史,工作原理以及优缺点,对XMLHttpRequest对象进行了详细的讲解,并使用原生js实现了一个ajax对象以方便日常开始使用. damonare的ajax库: ...
- “三次握手,四次挥手”你真的懂吗?TCP
“三次握手,四次挥手”你真的懂吗? mp.weixin.qq.com 来源:码农桃花源 解读:“拼多多”被薅的问题出在哪儿?损失将如何买单? 之前有推过一篇不错的干货<TCP之三次握手四次挥手 ...
- 你真的懂 ajax 吗?
前言 总括: 本文讲解了ajax的历史,工作原理以及优缺点,对XMLHttpRequest对象进行了详细的讲解,并使用原生js实现了一个ajax对象以方便日常开始使用. damonare的ajax库: ...
- 【转】先说IEnumerable,我们每天用的foreach你真的懂它吗?
[转]先说IEnumerable,我们每天用的foreach你真的懂它吗? 我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么Linq ...
- 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截
程序猿修仙之路--数据结构之你是否真的懂数组? 数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构 .要想在之后的江湖历练中通关,数据结构必不可少. ...
- C# 知识回顾 - 你真的懂异常(Exception)吗?
你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...
随机推荐
- 项目中碰到的ExceptionInInitializerError异常
背景 之前在集成第三方即时通信系统-融云的时候,我直接clone它的服务端源码,然后导入我的项目,我在测试它连接融云服务器案例时,发现一直不成功,始终报一个 ExceptionInInitialize ...
- Android——Intent详解
Intent组件虽然不是四大组件,但却是连接四大组件的桥梁,学习好这个知识,也非常的重要. 一.什么是Intent 1.Intent的概念: Android中提供了Intent机制来协助应用间的交互与 ...
- js之美
http://fxck.it/post/72326363595 大部分脚本语言里字面量都要快一些,因为解析[]比解析new Array()步骤少的多 关于Object可以用constructor注入的 ...
- 升级cocoapods 0.36.0之后,解决更新的部分依赖库,不是最新版本号的问题
在升级到cocoapods 0.36.0之后,使用原本podfile配置,在又一次运行pod install之后,一些依赖库的版本号,低于github上的最新版本号.可依照下列配置.指定git,强制使 ...
- 简单推导 PCA
考虑二维数据降低到一维的例子,如下图所示: 最小化投影方差(maximize projected variance): 1N∑n=1N(uuT1xn−uuT1x¯)=uuT1Suu1,s.t.uuT1 ...
- VC中引用第三方库,常见的库冲突问题
Q:VC中引用第三方库,常见的库冲突问题 环境:[1]VS2008 [2]WinXP SP3 A1(方法一): [S1]第三方库(Binary形式的)如果同主程序冲突,则下载第三方库的源码[S2]保持 ...
- PYC文件简介
PYC文件简介¶ 不说废话,这里说的pyc文件就是 Python 程序编译后得到的字节码文件 (py->pyc). 基本格式¶ pyc文件一般由3个部分组成: 最开始4个字节是一个Maigc i ...
- JDBC学习笔记——增删改查
1.数据库准备 要用JDBC操作数据库,第一步当然是建立数据表: ? 1 2 3 4 5 6 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_I ...
- MySQL第五个学习笔记 该数据表的操作
MySQL在创建表,创建.frm文件保存表和列定义.索引存储在一个.MYI(MYindex)且数据存储在有.MYD(MYData)扩展名的文件里. 一.用SHOW/ DESCRIBE语句显示数据表 ...
- 入骨三分的“我们是谁”IT行业版
这组漫画用简单的台词.粗犷的线条,把很多人心中对甲方(客户)的不满彻底地发泄了一通,并且出现了很多变种版本,引发了传播热潮. http://news.sina.com.cn/c/nd/2017-08- ...