对C语言中经常使用的printf这个库函数,你是否真的吃透了呢?

系统化的学习C语言程序设计,是不是看过一两本C语言方面的经典著作就足够了呢?答案是显而易见的:不够。通过这种典型的入门级的学习方式,是不可能真正吃透C语言的。如果C语言这么容易就被吃透了,那么C语言就不可能成为程序设计语言之母,也不可能被各种操作系统用来作为系统调用的API接口语言。学习任何一门技术,如果只是会使用,而不知道其背后的原理,那么就只是知其然不知其所以然。本文试图通过回答一个跟printf有关的“为什么”的问题,来讨论一下简单用法背后的C语言标准中的一个与这个问题相关的技术概念。

为什么printf能够使用%f这个格式化字符串来输出float和double类型的参数?

先看代码例子。

printf("%f %f", 1.234f, 1.234);

乍一看,很多人会觉得这个代码简直天经地义,C语言入门者就已经能够正确的写出这种“如此简单”的代码。这又有什么值得大惊小怪的呢?

现在的问题是为什么上面这个语句能够正常运作?你是否真的深入思考过这个问题呢?

在回答这个为什么的问题之前,先来看另外一个为什么的问题:为什么要思考这个问题呢?

请注意,参数中对1.234f和1.234的输出使用的格式化字符串都是%f。

printf函数的格式化字符串有两个作用:

作用1:指示输出格式。这个是printf格式化字符串不可或缺的作用。

作用2:指示后面的参数值的数据类型。printf函数内部必须能够精确的知道后面传入的参数的类型,否则无法通过va_arg取得实参的值。这个作用是至关重要的。

上面这个代码显然是没有任何问题的。至此,不由得思考一个问题,printf如何能够取得后面两个参数的数据类型呢?printf不可能根据同样一个%f,一会得出后面的参数是float类型的推测,一会得出后面的参数是double的推测。printf能利用的已知信息就只有%f这个格式化字符串。有人说,后面写1.234f就是float,写1.234就是double啊。这个信息仅仅对函数调用方有效,对于被调用函数printf是不可见的。即printf确实无法分辨出后面两个参数的数据类型是什么。

那么printf究竟怎么就知道了两个参数的数据类型的呢?printf肯定是已经知道了数据类型的,否则无法用va_arg取得实参的值。这其实不是printf自身的特异功能,完全是沾了C语言标准的光。这就是神奇的默认参数类型提升。正是有了默认参数类型提升这个概念,编译器在编译上面这个代码时,首先将1.234f这个float类型的参数转化为了double类型的参数。那么printf在处理%f这个格式化字符串时,直接认为对应的参数是double类型即可完成工作,根本不需要区分实际参数是float还是double的问题。

既然是C语言标准提出的这个概念,那么很多C语言编译器就支持了这个特性。包括gcc和vs2019。而且,我们自己写一个变长参数的函数时,也必须考虑这个特性,否则所写函数无法正常工作。当然,当使用float作为参数调用va_arg时,编译器会编译错误的,因此也不必过于担心这个问题带来的困扰。问题在于当我们不了解这个神奇的概念时,在出现类似编译错误时,我们就无法理解代码到底为什么会产生编译错误。

实际上对于printf的格式化字符串,还有整数的格式化输出的问题。比如:

printf("%d %d %d",  (short)1, (char)2, 3);

这又是一个C语言入门者就已经“熟练掌握”的代码。这其中同样存在前面关于%f类似的问题。printf怎么知道后面的3个参数的实际类型呢?要回答这个问题,同样需要理解前面提到的默认参数类型提升的概念。默认参数类型提升不仅对于float类型有效,对于char和short类型,同样会提升,不同在于前者执行的是浮点数类型提升,后者执行的是整数类型提升。因此,char和short类型都会先转化为int类型,再传递给printf函数。这样printf只需要把%d这个格式化字符串对应的实参理解为是int类型即可,这就解决了所有的实参类型的识别的问题。

一个简单的printf函数,背后竟然藏着这么一个神奇的技术概念,而且这个概念还是在C语言标准中明确描述的。现在,你了解了上面那个printf语句为什么能够正常运作了吗?

现在,你还认为printf这个库函数很简单吗?你还自信满满的认为自己精通C语言吗?

你真的很了解printf函数吗?的更多相关文章

  1. C 中 关于printf 函数中度剖析

    题外话  这篇博文主要围绕printf函数分析的,主要讲解printf 使用C的可变参数机制, printf是否可重入(是否线程安全), printf函数的源码实现. 正文 1.C中可变参数机制 我们 ...

  2. 可变参数列表与printf()函数的实现

    问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...

  3. 三,对于printf函数和C语言编程的初步拓展

    前面说过了,任何程序都要有输出,所以printf函数是一个很重要的函数,所以有必要在学变量之前先拓展一下. 其实编程就是用计算机语言说话,一句一句地说,只要语法没错就能运行,至于能实现什么功能,就看编 ...

  4. 【stm32】实现STM32的串口数据发送和printf函数重定向

    在调试电机驱动程序的时候,是不能随便利用中断来进行一些寄存器或数据的查看的,不然你在运行的时候突然来一下,如果占空比大的话那可能直接就把MOS管给烧了,所以我们很多情况下只能使用USART(串口)来进 ...

  5. 考察printf函数返回值

    最近偶然间见了这样一道题:  #include<stdio.h> int main() { ; printf("%d\n",printf("%d", ...

  6. 初识C(2)---从printf函数开始

    继承[K&R]的传统,我们的第一个C语言程序也是“Hello, World.”. 书写C语言程序的大前提:C语言中的语法符号必须都是英文字符,即在中文输入法关闭状态下输入的字符. 例 1. H ...

  7. [转]printf 函数实现的深入剖析

    研究printf的实现,首先来看看printf函数的函数体 int printf(const char *fmt, ...) { int i; char buf[256];          va_l ...

  8. 【C语言】浅谈可变参数与printf函数

    一.何谓可变参数 int printf( const char* format, ...); 这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用& ...

  9. printf函数压栈解惑

    最近看到一些程序员的笔试题目,经常会考到printf函数的参数压栈问题,总体来讲就是参数从右向左依次压栈,再出栈,但是今天看到一个看似很简单的题目,却一直找不到头绪.题目如下: #include &l ...

随机推荐

  1. [翻译] Cassandra 分布式结构化存储系统

    Cassandra 分布式结构化存储系统 摘要 Cassandra 是一个分布式存储系统,用于管理分布在许多商品服务器上的大量结构化数据,同时提供无单点故障(no single point of fa ...

  2. Centos7 搭建 Socks 服务

    Centos7 搭建 Socks 服务 一丶拿到一个动态拨号的服务器还不能使用网络得先打开: pppoe-start 二丶安装命令汇总: 通过yum安装ss5 依赖包: yum install gcc ...

  3. gin框架使用【5.路由分组】

    package mainimport ( "github.com/gin-gonic/gin")func main() { router := gin.Default() v1Ro ...

  4. 网络协议自动化逆向工具开山鼻祖discoverer 分析

    本文系原创,转载请说明出处:信安科研人 也可关注微信公众号:信安科研人 原论文发表在2007年的USENIX上,链接如下:https://www.usenix.org/legacy/event/sec ...

  5. Android第1-2周作业

    作业1:安装环境,截图编程界面,截图运行界面 作业2:九宫格 <?xml version="1.0" encoding="utf-8"?> < ...

  6. html 移动端与PC端之间的转换

    href 里面写路径 if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackB ...

  7. tensorflwo-gpu win10_64bit 的安装版本问题

    tensorflow 1.3 配 cuda8.0 + cudnn5.1tensorflow 1.4 配 cuda8.0 + cudnn6.0 有没有更大的字体???我要配!!!!!

  8. 腾讯tbs 内存泄露

    一.背景 TBS(腾讯浏览服务)是腾讯提供的移动端webview体验的整套解决方案(https://x5.tencent.com/docs/index.html),可以用于移动端加载doc.xls.p ...

  9. Spring Authorization Server 实现授权中心

    Spring Authorization Server 实现授权中心 源码地址 当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持.Spring Authorizati ...

  10. VS.NET启动显示ID为XXXX的进程当前未运行

    解决办法:在启动项目根目录下用文本编辑器打开Web项目下的{X}.csproj文件,然后查找 <WebProjectProperties>,将这一对标签之间的内容全部删除,然后再打开项目就 ...