printf和println区别_c语言是在什么语言基础上产生的

printf和println区别_c语言是在什么语言基础上产生的【原创】计算机自制操作系统(Linux篇)五:内核开发之万丈高楼从地起-printk(理清pintf/vprintf;sprintf/vsprintf ;fprintf/vfprintf)本章开始,我将进入自制Linux操作系统的内核开发。由于操作系统内核涉及的内容

【原创】计算机自制操作系统(Linux篇)五:内核开发之万丈高楼从地起—printk(理清pintf/vprintf;sprintf/vsprintf ;fprintf/vfprintf)   本章开始,我将进入自制Linux操作系统的内核开发。由于操作系统内核涉及的内容太多,因此后面的风格将会大幅降低文字篇幅,以记录和讲解思路为主。   一、printk   操作系统内核开发的第一步是必须要实现字符串打印print,有了打印功能才能实现与开发者的交互。前面我们在boot目录下汇编引导启动程序,实现的字符串打印比较低级:实模式下用的BIOS中断、保护模式下是直接操作的显存地址:0xb8000。内核开发是大型化的模块程序工程,因此我们必须要重新包装定义一个字符串打印功能函数,由于这个打印功能是专用于内核程序使用的,因此取名叫printk(print kernel)。   你一定之前写程序只接触过printf,从来不知道还有一个printk。这不奇怪,因为你只写过应用软件,没有写过操作系统。   有人会问,既然是在用标准的C语言开发内核,我们都知道C语言提供了标准打印函数pintf,为什么不直接使用?原因:1.C语言标准函数库需要归属的操作系统支撑,我们现在是自制操作系统,当然无法支持其函数应有的功能。2.C语言标准函数库空间巨大,我们自制操作系统的总内核大小规划在1MB以下的空间,如果将C标准库类似#include <stdio.h>链接进去,恐怕早就撑爆。所以开发操作系统内核,C语言标准库都是不能使用的,必须自己动手。   (一) 普通字符串打印   首先实现类似printk(“hello word”)的普通字符串功能,思路如下:   1.实时和更新屏幕光标位置:每次打印字符的时候,直接打印在光标位置处,打印完成之后光标挪动位置,形成光标始终跟随字符的效果。由于光标位置操作涉及端口,因此需要把它放在内核的汇编程序kernela.asm中。   2.控制字符显示逻辑:如果一行显示满了,需要换行;如果遇到回车符,需要换行;如果遇到所有的行都显示满了,需要滚屏。   (二) 带变量的字符串打印   1.可变参数函数   软件开发最重要的功能就是能随时打印变量,这样才方便调试。所以我们还必须实现printk类似下面的功能:   打印效果可能是:Info:Cursor Positon—800。要实现这个功能就是有点复杂,可以看出这时printk函数变成了C语言可变参数函数:它的参数个数是不固定的,需要具体每次调用的时候才能确定。   可变参数怎么实现呢?这个时候,我们就可以照搬C语言标准库函数printf的实现方法了,因为它就是一个很标准的可变参数函数,打开C语言最新版本的printf可以看它的原型:   int printf(const char * __restrict__ _Format,…)   是一个可变参数函数,表示参数格式不固定,因此参数列表中用…表示。   2. vsprintf   我们来研究一下C标准库函数printf的实现过程:在每一次调用过程中,printf必须有一种方法来使用这些参数才行。printf使用了它的第一个参数fmt(格式化实参字符串的首地址(指针))作为基准,得到了后面若干参数的开始地址,这样,其值也就容易得到了。如假设我们调用printf(fmt, var3, var2, var1),在刚进入printf的内部代码时,堆栈内的数据情况是:
printf和println区别_c语言是在什么语言基础上产生的
printf和println区别_c语言是在什么语言基础上产生的   由于格式化实参字符串fmt的参数个数是不固定的,有多又少,所以需要定义一个新的缓冲区buf字符串,用来存放最终的输出字符串。那么怎么把格式化实参字符串fmt转化成最终的缓冲区buf字符串呢?   方法就是遍历实参字符串fmt的内容,凡是遇到字符”%”的地方,就知道函数调用的参数来了,于是就停下来去堆栈里面找到这个参数实际的值,并将这个参数值转换成参数字符串,用参数字符串去实参字符串fmt中对应的位置把“%?”的内容替换掉即可。每处理完成一个参数后,参数在堆栈里的位置就加4(见上图)。重复这样的过程,至到所有的参数全部替换完毕,这样得到的缓冲区buf字符串就是最终的显示内容!   以上过程的本质实际就是将不固定长度的字符串(有可变参数)转换成固定长度字符串,要完成这样一个字符串转换功能,C标准库有一个专门的函数:vsprintf。我们打开C标准库,可以看一下这个函数的原型:   int vsprintf(char * __restrict__ _Dest,const char * __restrict__ _Format,va_list _Args)   它有三个参数:明显第1个参数就是我们上面说的buf,第2个参数就是上面说的实参字符串fmt,第3个参数就是上面说的第1个可变参数在栈内的地址:(char*)(&fmt)+4。但是C标准库又对第3个参数改变了一个写法:va_list args。什么意思呢?都说了args=(char*)(&fmt)+4。所以args其实就是一个char类型的指针,C标准库通过通过宏定义来表示的。另外,它还定义2个宏:va_start(args, format),va_end(args)来分别表示参数首地址和末尾地址。一般来讲,在C语言标准库里面,是专门用一个stdarg.h的头文件来定义它们的:   有了这些准备工作,我们就来看C标准库的printf是怎么实现的:   可以清楚的看到:printf是通过vsprintf来把不固定参数个数的字符串转换成固定字符串的,vsprintf的作用就是识别出全部的实际参数并将其值取出来组装完毕之后存储到buf区,最后再由系统调用函数write()将buf里面的字符串全部显示出来。有兴趣的可以继续参考vsprintf源代码来了解它是如何实现%d,%x,%s等一些列格式转换的。   3. printk可变参数实现   搞懂了以上printf的实现过程,我们照搬C标准库printf调用vsprintf的做法,printk也通过调用vsprintf的方式来实现:   这样一来,我们通过对printk的以下调用,最终就实现了变量字符串打印,看一下效果:   
printf和println区别_c语言是在什么语言基础上产生的
printf和println区别_c语言是在什么语言基础上产生的   另外:在写这篇文章中,我发现了《操作系统真象还原》书中的一个bug:就是函数vsprintf(buf, format, args)在实现的时候,源代码中忘记对最终的目标字符串buf添加结束标记了,这样会在实际的运行中打印变量会出问题,因此需要在最后2行添加如下代码:   4. sprintf函数   上面printk的实现过程很明显:是将变长的format参数字符串存入了一个临时字符串buf,最终把这个buf送到显示器打印出来。有时候,我们可能不需要将buf送到显示器,只需要将字符串记录在buf中即可,因此C标准库就提供了这样一个函数:sprintf:   int sprintf(char * __restrict__ _Dest,const char * __restrict__ _Format,…)   很明显,它比printk多一个参数:Dest,因为需要指定最终字符串的目标位置buf嘛,这个函数运行之后,可变参数就全部规整成格式字符串Format的样子,并存入了目标字符串位置:Dest。sprintf的具体实现和printk如出一辙,只不过少了一个送到显示器打印的过程。   这样一来,我们在内核程序中,既可以调用可变参数函数printk进行直接打印,也可以通过调用可变参数函数sprintf来进行间接打印,它们的效果都是一样的:   等效于:   sprintf这个函数,我们在上一个专栏《计算机自制操作系统(Windows篇)》也中有频繁使用。   二、printf   前面说过了,你一定之前写程序只接触过printf,这是应用软件的打印函数。它和printk的使用场景有本质区别:printk是操作系统内核打印函数,它只能在内核代码中使用。很明显,使用者调用它的时候,CPU运行特权级在内核级0级。printf是应用程序打印函数,它只能在应用程序中使用。很明显,使用者调用它的时候,CPU运行特权级在应用级3级。   但是由于内核中打印字符串的函数printstr是所有打印函数的基础,应用程序printf要完成打印功能,最终是要执行内核程序中的printstr才行。应用程序特权级是3级,内核程序特权级是0级,所以printf要完成调用printstr,必须进行系统调用来做特权级转换才行!所以,最终printf的实现逻辑如下,它和printk的关系也就一目了然:
printf和println区别_c语言是在什么语言基础上产生的
printf和println区别_c语言是在什么语言基础上产生的   三、print家族   print家族成员包括:pintf/vprintf;sprintf/vsprintf;fprintf/vfprintf。通过前面的推导过程,我们至少已经明白了:pintf、sprintf和vsprintf。   可以看到,print类函数的标准格式是:”A+print+B”,其中B为”f”,意为format的首字母,意思是格式化输出。   A的基本分类有3种:空、s和f,含义如下:空:这是默认的情况,函数是pintf/vprintf,代表的含义是向标准化设备(显示器)输出。s: string的缩写,函数有sprintf/vsprintf,代表的含义是向字符串缓冲区输出。f: file的缩写,函数有fprintf/vfprintf,代表的含义是向文件输出。v:v是va_arg的缩写,是指这个函数接受va_list的参数。通过我们前面的论述,明显它本质上就是将可变参数的格式调用变成固定参数调用,最终实现C语言的可变参数函数。   那么最终,通过以上组合就得到两大类printf函数:可变参数类函数:pintf/sprintf/fprintf接收可变参数的固定参数函数:vpintf/vsprintf/vfprintf

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/68119.html

(0)
上一篇 2024年 8月 8日
下一篇 2024年 8月 8日

相关推荐

关注微信