首先,要介绍一下printf实现的原理

printf函数原型如下:
int printf(const char* format,...);

返回值是int,返回输出的字符个数。

例如:
int main()
{
int n;
n=printf();
printf("返回值:%d\n",n);
;
}

测试结果:

hello world,

返回值:

测试结果是16,是因为100虽然是整型数,但是输出时计算返回值它是3个字符。

参数format是一个字符指针,指向printf里的第一个字符串。

参数...是不定参数。这是printf能够实现的核心。

接下来介绍一下不定参数是如何实现的。

int printf(const char* format,...);

函数的参数由右向左依次入栈,如下图:

比如我们printf实际输入的参数有4个,printf(char* format,arg1,arg2,arg3,arg4);

这些参数在内存中从低地址到高地址依次为format,arg1,arg2,arg3,arg4。

因为format是指针,所以所占的字节大小为一个int的大小。

所以如果我们找到format的储存地址,从format首地址开始,加上一个int的大小,此时地址刚好就是参数arg1的首地址,然后再加上sizeof(arg1),此时地址又刚好是arg2的首地址,这样我们就能依次找出参数所在地址。

具体实现时,我们只需要定义一个指针变量ap指向arg1参数的起始地址,同时分析format参数所指的字符串,从字符串第一个字符开始检查,如果遇到%则通过分析%后面的字符就能判断出变量的类型,此时输出ap地址上所指向的变量的值,同时ap指针向右移动该变量类型大小字节个单位,使ap指向下一个参数的储存地址,然后再次分析字符串,直到分析到字符串结尾结束。

通过上面的参数入栈方式我们可以得到如下结论:

如果想将栈中的参数读出来,我们只需要知道,栈顶元素的地址即第一个参数的地址即可。通过前面变参函数的分析,通过变参函数第一个参数可以知道传递的参数个数。

当然,每个参数都有自己的类型,还有的就是字节对齐了。在读取参数的时候,这些问题都必须考虑到。

实际上处理变参时,已经有封装好的宏处理这些所有问题

typedef char * va_list; //将char*别名为va_list;

#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) 

#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))

#define va_arg(ap,t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

#define va_end(ap) (ap = (va_list)0)

这些宏在不同的操作系统,有不同的实现,想使用的话,只需要包含头文件stdarg.h就可以了。

()va_start宏的作用 :

printf(const char* format,arg1,agr2,....)

实现ap指向第一个实际参数arg1的地址,实际参数指第一个参数format后的第一个参数arg1。即va_start(ap,format)。

()va_arg宏作用:

t指的是分析出来的实际参数的变量类型,首先ap向后移动sizeof(t)个单位,指向下一个实际参数的地址,同时返回(ap-sizeof(t))的地址,返回的地址跟刚开始时地址一样。实际上就是为了ap移动到下一个参数的地址,为了下一次输出。

()va_end宏的作用

将ap指针赋值为NULL,即0

看一下实现代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdarg.h>

void printch(const char ch) //输出字符
{
putchar(ch);
} 

void printint(const int dec) //输出整型数
{
)
{
return;
}
printint(dec / );
putchar(( + '));
} 

void printstr(const char *ptr) //输出字符串
{
while(*ptr)
{
putchar(*ptr);
ptr++;
}
} 

void printfloat(const float flt) //输出浮点数,小数点第5位四舍五入
{
int tmpint = (int)flt;
 * (flt - tmpint));
 >= )
{
tmpflt = tmpflt /  + ;
}
else
{
tmpflt = tmpflt / ;
}
printint(tmpint);
putchar('.');
printint(tmpflt); 

} 

void my_printf(const char *format,...)
{
va_list ap;
va_start(ap,format); //将ap指向第一个实际参数的地址
while(*format)
{
if(*format != '%')
{
putchar(*format);
format++;
}
else
{
format++;
switch(*format)
{
case 'c':
{
char valch = va_arg(ap,int); //记录当前实践参数所在地址
printch(valch);
format++;
break;
}
case 'd':
{
int valint = va_arg(ap,int);
printint(valint);
format++;
break;
}
case 's':
{
char *valstr = va_arg(ap,char *);
printstr(valstr);
format++;
break;
}
case 'f':
{
float valflt = va_arg(ap,double);
printfloat(valflt);
format++;
break;
}
default:
{
printch(*format);
format++;
}
}
}
}
va_end(ap);
} 

int main()
{
char ch = 'A';
char *str = "hello world";
;
float flt = 1234.45678;
my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt);
;
}

 运行结果:

ch = A,str = hello world,dec = ,flt = 1234.4568

实际上,实现时可以用一个更简单的函数,vprintf函数。

int vprintf(char *format, va_list param);

printf的功能就是用它来实现的,所不同的是,它用一个参数取代了变长参数表,且此参数是通过调用va_start宏进行初始化。其实vprintf也是经过封装的一个函数。
这样就省了我们调用宏对变参函数进行处理,只要开始调用一次va_start宏进行一次初始化即可。

代码如下:

#include<stdio.h>
#include<stdarg.h>

int my_printf(char *str,...)
{
int n; //记录返回值
va_list list;
va_start(list,str);
n=vprintf(str,list);
va_end(list);
return n;
}

int main()
{

my_printf();

}

运行结果:

hello world,

---------------------
作者:阿鑫.
来源:CSDN
原文:https://blog.csdn.net/fengxinlinux/article/details/52064816
版权声明:本文为博主原创文章,转载请附上博文链接!

实现简单的printf函数的更多相关文章

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

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

  2. printf 函数的实现原理

    /* * ===================================================================================== * * Filen ...

  3. 你真的很了解printf函数吗?

    对C语言中经常使用的printf这个库函数,你是否真的吃透了呢? 系统化的学习C语言程序设计,是不是看过一两本C语言方面的经典著作就足够了呢?答案是显而易见的:不够.通过这种典型的入门级的学习方式,是 ...

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

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

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

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

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

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

  7. 研究不定数量参数的函数并实现一个printf函数

    一.前提知识 1.如何传递参数(主函数) a.函数的参数是通过栈传递,而且是从右到左依次入栈 b.即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的. c.sho ...

  8. 在keil中使用printf()函数的要点

    在keil中printf默认是向串口中发送数据的,所以,如果应用该函数,必须先初始化串口,否则可能引起死机的情况,并且在printf之前应该先将TI置位,摘抄原因如下: 1.printf函数是调用pu ...

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

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

随机推荐

  1. Eclipse查看JDK源码(非常详细)

    Eclipse查看源码的方式其实很简单,打开项目,然后按着ctrl,然后把鼠标光标移动到你想查看的方法或者对象上,这时会出现一条下划线,然后点击鼠标左键就可以进入那个方法或者对象了.但是有的情况下会出 ...

  2. onload 和 domready

    博客地址:https://ainyi.com/46 window.onload 事件会在页面或图像加载完成后触发(即所有元素的资源都下载完毕)如果页面上有许多图片.音乐或 falsh 还没加载完成,o ...

  3. Java高阶语法---Volatile

    背景:听说Volatile Java高阶语法亦是挺进BAT的必经之路. Volatile: volatile同步机制又涉及Java内存模型中的可见性.原子性和有序性,恶补基础一波. 可见性: 可见性简 ...

  4. sqlserver操作geography方法

    参考:https://www.cnblogs.com/ytwy/p/5977848.html http://desktop.arcgis.com/zh-cn/arcmap/latest/manage- ...

  5. #WEB安全基础 : HTML/CSS | 0x10实现交互_表单

    先看看表单如何工作吧 请求   响应   简要工作流程: 浏览器加载页面 用户输入数据 用户提交表单 服务器响应 概念都清楚了,我们来写表单吧 只有一个html文件   这是显示   你可以向空白框框 ...

  6. 2018 AI产业界大盘点

    2018  AI产业界大盘点 大事件盘点 “ 1.24——Facebook人工智能部门负责人Yann LeCun宣布卸任 Facebook人工智能研究部门(FAIR)的负责人Yann LeCun宣布卸 ...

  7. C# 发送电子邮件源码片段

    下面代码内容是关于C# 发送电子邮件片段的代码,应该对各位有所用途. using System;using System.Web;using System.Web.Mail;public class ...

  8. Android 中使用 dlib+opencv 实现动态人脸检测

    1 概述 完成 Android 相机预览功能以后,在此基础上我使用 dlib 与 opencv 库做了一个关于人脸检测的 demo.该 demo 在相机预览过程中对人脸进行实时检测,并将检测到的人脸用 ...

  9. ASP.NET Core 入门教程 4、ASP.NET Core MVC控制器入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC控制器简介 ASP.NET Core MVC控制器操作简介 ASP.NET Core MVC控制器操作简介返回类型简介 ASP.NET C ...

  10. 推荐一款关于MongoDB日志分析的工具--Mtools

    一. 需求背景 MongoDB数据库的强大的文档模型使其成为处理数据的最佳方式.文档适用于广泛的流行数据模型,支持各种各样的场景.文档模型可以包含键值.关系数据集和图形数据集,当然,还可以包含父子关系 ...