本文仅做理性上的愉悦,无实际用途。

scanf实际的调用

我们直接使用的scanf其实是这样写的

int __cdecl scanf (
const char *format,
...
)
{
va_list arglist;
va_start(arglist, format);
return vscanf_fn(_input_l, format, NULL, arglist);
}

我们可以看到,他其实是调用了这三个函数: va_list va_start vscanf_fn

我们跳转到vscanf_fn的实现

int __cdecl vscanf_fn (
INPUTFN inputfn,
const char *format,
_locale_t plocinfo,
va_list arglist
)
/*
* stdin 'SCAN', 'F'ormatted
*/
{
int retval = 0; _VALIDATE_RETURN( (format != NULL), EINVAL, EOF); _lock_str2(0, stdin);
__try {
retval = (inputfn(stdin, format, plocinfo, arglist));
}
__finally {
_unlock_str2(0, stdin);
} return(retval);
}

我们发现,vscanf_fn实际上是使用了inputfn这个函数。我们进一步跟进,找到了input.c这个文件

/***
*int _input(stream, format, arglist), static int input(format, arglist)
*
*Purpose:
* get input items (data items or literal matches) from the input stream
* and assign them if appropriate to the items thru the arglist. this
* function is intended for internal library use only, not for the user
*
* The _input entry point is for the normal scanf() functions
* The input entry point is used when compiling for _cscanf() [CPRFLAF
* defined] and is a static function called only by _cscanf() -- reads from
* console.
*
* This code also defines _input_s, which works differently for %c, %s & %[.
* For these, _input_s first picks up the next argument from the variable
* argument list & uses it as the maximum size of the character array pointed
* to by the next argument in the list.
*
*Entry:
* FILE *stream - file to read from
* char *format - format string to determine the data to read
* arglist - list of pointer to data items
*
*Exit:
* returns number of items assigned and fills in data items
* returns EOF if error or EOF found on stream before 1st data item matched
*
*Exceptions:
*
*******************************************************************************/

有几个关键函数:

static _TINT __cdecl _inc(FILE* fileptr)
{
return (_gettc_nolock(fileptr));
}

_inc的功能是调出缓冲区第一个字符

static void __cdecl _un_inc(_TINT chr, FILE* fileptr)
{
if (_TEOF != chr) {
_ungettc_nolock(chr,fileptr);
}
}

_un_inc函数,将刚才_inc调出的函数重新放进缓冲区

static _TINT __cdecl _whiteout(int* counter, FILE* fileptr)
{
_TINT ch; do
{
++*counter;
ch = _inc(fileptr); if (ch == _TEOF)
{
break;
}
}
while(_istspace((_TUCHAR)ch));
return ch;
}

_whiteout函数,将从缓冲区开头开始的所有连续空白字符调出

最后一步检查缓冲区,如果缓冲区可读字符为0,那么清空缓冲区

format的解析

while (*format) {

        if (_istspace((_TUCHAR)*format)) {

            UN_INC(EAT_WHITE()); /* put first non-space char back */

            do {
tch = *++format;
} while (_istspace((_TUCHAR)tch)); continue;
………………

这里的UN_INC(EAT_WHITE()),是把当初EAT_WHITE读出的第一个非空白字符再放入缓冲区。

上面代码完成对键盘缓冲区中空白符的清理,直到正常读取第一个字符。

当读入%号,进行处理:

if (_T('%') == *format && _T('%') != *(format + 1))

解析完格式后,开始真正的实现,我们以%d做例子:

if (_T('^') == *scanptr) {
++scanptr;
--reject; /* set reject to 255 */
} /* Allocate "table" on first %[] spec */
#if ALLOC_TABLE
if (table == NULL) {
table = (char*)_malloc_crt(TABLESIZE);
if ( table == NULL)
goto error_return;
malloc_flag = 1;
}zuolizi
#endif /* ALLOC_TABLE */
memset(table, 0, TABLESIZE); if (LEFT_BRACKET == comchr)
if (_T(']') == *scanptr) {
prevchar = _T(']');
++scanptr; table[ _T(']') >> 3] = 1 << (_T(']') & 7); } while (_T(']') != *scanptr) { rngch = *scanptr++; if (_T('-') != rngch ||
!prevchar || /* first char */
_T(']') == *scanptr) /* last char */ table[(prevchar = rngch) >> 3] |= 1 << (rngch & 7); else { /* handle a-z type set */ rngch = *scanptr++; /* get end of range */ if (prevchar < rngch) /* %[a-z] */
last = rngch;
else { /* %[z-a] */
last = prevchar;
prevchar = rngch;
}
/* last could be 0xFF, so we handle it at the end of the for loop */
for (rngch = prevchar; rngch < last; ++rngch)
{
table[rngch >> 3] |= 1 << (rngch & 7);
}
table[last >> 3] |= 1 << (last & 7); prevchar = 0; }
}

reject反转标记,如果出现^ 则reject = FF; 其后方便进行 ^ 进行反转。

对于[ ]字符集,有一个char table[32]来保存256个ascii字符。(此处每个char为8bits,所以有32组可以完全包含256个ascii字符)

微软对table中字符做了这样的处理:

table[rngch >> 3] |= 1 << (rngch & 7);

即:将所读的字符串分到32组中【rngch>>3相当于除以8】,每个table[n]有8bits,每个bit中,出现的字符位会被置为1,未出现则为0,这样就完美囊括了256个ASCII字符。

判断字符是否存在,直接这样处理:

(table[ch >> 3] ^ reject) & (1 << (ch & 7))

在其中有很多判断_ISXDIGIT(ch)的,假若不是阿拉伯数字,则会执行跳出当前%d字符读取,执行1313行的 ++format; /* skip to next char */

%d跳过了\n的读取,继续读取下一个字符。

if (_T('%') == *format && _T('%') != *(format + 1)) {

		……………………

		   ++format;  /* skip to next char */
} else /* ('%' != *format) */
{
………………………
}

在读代码时候读到一个函数 hextodec

static _TINT __cdecl _hextodec ( _TCHAR chr)
{
return _ISDIGIT(chr) ? chr : (chr & ~(_T('a') - _T('A'))) - _T('A') + 10 + _T('0');
}

将读取的16进制字符 0 - F转成 10进制数,然后scanf就结束了

我们有一个非常简单的scanf的实现(臭不要脸的调用vscanf_fn)

#include<cstdio>
#include<cstdarg>
int my_scanf(char* fmt,...)
{
int ret=0;
va_list args;
va_start(args,fmt);
vscanf(fmt,args);
va_end(args);
return ret;
}
int main()
{
int a;
my_scanf("%d",&a);
printf("%d",a);
return 0;
}

【文文殿下】浅析scanf源码的更多相关文章

  1. 浅析Java源码之ArrayList

    面试题经常会问到LinkedList与ArrayList的区别,与其背网上的废话,不如直接撸源码! 文章源码来源于JRE1.8,java.util.ArrayList 既然是浅析,就主要针对该数据结构 ...

  2. 浅析Java源码之LinkedList

    可以骂人吗???辛辛苦苦写了2个多小时搞到凌晨2点,点击保存草稿退回到了登录页面???登录成功草稿没了???喵喵喵???智障!!气! 很厉害,隔了30分钟,我的登录又失效了,草稿再次回滚,不客气了,* ...

  3. 浅析libuv源码-获取精确时间

    在Timer模块中有提到,libuv控制着延迟事件的触发,那么必须想办法精确控制时间. 如果是JS,获取当前时间可以直接通过Date.now()得到一个时间戳,然后将两段时间戳相减得到时间差.一般情况 ...

  4. 浅析libuv源码-编译启动

    面试的间隙回头复习了一下node,感觉node就像一个胶带,把V8和libuv粘在了一起. V8毫无疑问,负责解析执行JavaScript,相当于语言层面的桥梁:而libuv则是负责操作系统底层功能的 ...

  5. 浅析Java源码之HttpServlet

    纯粹是闲的,在慕课网看了几集的Servlet入门,刚写了1个小demo,就想看看源码,好在也不难 主要是介绍一下里面的主要方法,真的没什么内容啊~ 源码来源于apache-tomcat-7.0.52, ...

  6. 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)

    (这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...

  7. 浅析Java源码之HashMap

    写这篇文章还是下了一定决心的,因为这个源码看的头疼得很. 老规矩,源码来源于JRE1.8,java.util.HashMap,不讨论I/O及序列化相关内容. 该数据结构简介:使用了散列码来进行快速搜索 ...

  8. 浅析Java源码之Math.random()

    从零自学java消遣一下,看书有点脑阔疼,不如看看源码!(๑╹◡╹)ノ""" ​ JS中Math调用的都是本地方法,底层全是用C++写的,所以完全无法观察实现过程,Jav ...

  9. 浅析libuv源码-node事件轮询解析(3)

    好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...

随机推荐

  1. super限定,子类中系统查找变量的顺序:

    示例代码如下: import static java.lang.System.*; //-父类: class BaseClass{ public int a=7; } //-子类: public cl ...

  2. PAT 1003 我要通过!(20)(代码+思路)

    1003 我要通过!(20)(20 分)提问 "答案正确"是自动判题系统给出的最令人欢喜的回复.本题属于PAT的"答案正确"大派送 -- 只要读入的字符串满足下 ...

  3. PAT 1059 C语言竞赛(20)(代码+思路)

    1059 C语言竞赛(20 分) C 语言竞赛是浙江大学计算机学院主持的一个欢乐的竞赛.既然竞赛主旨是为了好玩,颁奖规则也就制定得很滑稽: 0.冠军将赢得一份"神秘大奖"(比如很巨 ...

  4. 201621123008 《Java程序设计》第六周实验总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 2. 书面作业 1. clone方法 1.1 在 ...

  5. 交互神器-最好用的Mac原型设计工具

    市场上有着大量的开发和设计工具支持在Mac上安装使用,今天给大家强烈推荐一款Mac上的原型设计工具-Mockplus,原型工具在产品开发设计中是必不可少的,无论是现在非常火的小程序设计,还是网页设计, ...

  6. hadoop 学习(三)之hdfs shell命令

    1.HDFS shell 1.0查看帮助  hadoop fs -help <cmd> 1.1上传  hadoop fs -put <linux上文件> <hdfs上的路 ...

  7. Perl注释文本的高亮显示规则

    sub help{ print <<EndOfUsage;\e[1;37mHELP :1. Usage :  perl $0 input output 2. Function : tran ...

  8. Javascript 常用扩展方法

    这篇文章纯粹是为了保存这些方法,供以后翻阅,其实一直保存在 evernote 里面,但觉得还是放到对的地方会好点. 现在收录的很少,希望以后会慢慢增多. 数组扩展 contains,remove 扩展 ...

  9. 2018上IEC计算机高级语言(C)作业 第0次作业

    最理想的师生关系是健身教练和学员的关系,在这种师生关系中你期望获得来自老师的哪些帮助? 最理想的的师生关系是健身教练和学员的关系,其实我个人感觉不太认同,我觉得老师和学生之间更多的是一种共生关系,像植 ...

  10. kafka系列 -- 多线程消费者实现

    看了一下kafka,然后写了消费Kafka数据的代码.感觉自己功力还是不够. 不能随心所欲地操作数据,数据结构没学好,spark的RDD操作没学好. 不能很好地组织代码结构,设计模式没学好,面向对象思 ...