使用scanf_s输入得到一行带空格的字符串

测试环境:VS2012 Update4
测试语言:C++
测试代码:

#include<stdio.h>
int main(int argc,char* argv[])
{
	char str[20];
	scanf_s("%s",str,18);
	return 0;
}

在第五行下断点,断下之后我们观察一下 str
地址:0x003DF9B8
内容如下,处于未初始化状态



接着F11进去,进入  scanf_s 函数
/***
*int scanf_s(format, ...) - read formatted data from stdin
*
*   Same as scanf above except that it calls _input_s_l to do the real work.
*   _input_s_l has a size check for array parameters.
*
*******************************************************************************/
int __cdecl scanf_s (
        const char *format,
        ...
        )
{
        va_list arglist;
        va_start(arglist, format);
        return vscanf(_input_s_l, format, NULL, arglist);
}


从注释中我们可以看到 scanf_s的功能是从stdin中读取格式化的数据
TA 和 scanf 的区别是用了_input_s_l函数,而这个函数有一个长度检查的功能(scanf用的是 _input_l)。
format :参数格式
arglist:参数列表,地址 => 0x003DF8E0
如图,arglist里存放的是 str 的地址和限制长度 0x12(18d)




接下来进入vscanf函数:

/***
*int vscanf(format, ...) - read formatted data from stdin
*
*Purpose:
*       This is a helper function to be called from fscanf & fscanf_s
*
*Entry:
*       INPUTFN inputfn - scanf & scanf_s pass either _input_l or _input_s_l
*                   which is then used to do the real work.
*       char *format - format string
*       va_list arglist - arglist of output pointers
*
*Exit:
*       returns number of fields read and assigned
*
*Exceptions:
*
*******************************************************************************/
int __cdecl vscanf (
        INPUTFN inputfn,
        const char *format,
        _locale_t plocinfo,
        va_list arglist
        )
/*
 * stdin 'SCAN', 'F'ormatted
 */
{
    int retval;
    _VALIDATE_RETURN( (format != NULL), EINVAL, EOF);
    _lock_str2(0, stdin);    //锁定输入
    __try {
        retval = (inputfn(stdin, format, plocinfo, arglist));
    }
    __finally {
        _unlock_str2(0, stdin);    //解锁输入
    }
    return(retval);
}

注释里说了,TA 的功能也 从 stdin 中读取格式化的数据
这是一个帮衬类型的函数, 由 fscanf和   fscanf_s调用

INPUTFN inputfn :由 scanf 或 scanf_s 传入的参数决定是用_input_l或者 _input_s_l .这个函数才是真正干活的 ! 自己都说了大实话。
constchar*format : 格式。
_locale_t plocinfo :注释里没说。。。
va_list arglist : 参数列表。

其中我们最多只能跟到__iob_func函数(起码我只能进来这里偷笑

这里面只有一句代码,将 _iob 返回.

/* These functions are for enabling STATIC_CPPLIB functionality */
_CRTIMP FILE * __cdecl __iob_func(void)
{
    return _iob;
}

那么我们就来看看 这个 _iob 指针是什么,在inputfn 还没有执行完毕,也就是我们还没有进行任何输入的时候是下面这样的~
貌似是一个地址哇~



进去 0x0F5FDE00看一下咯~嘛也没。。



现在我们输入一串字符 "Hello Kiya",按照 scanf_s 的特性我们知道最终 str 中存放的肯定只有 "Hello" 和一个 '\0'。
那么现在我们来看看这个    _iob 变成了什么样子
哎哟地址变了一点点



进来 0x0F5FDE05 看一下咯
我往上拖了一点,因为   0x0F5FDE00 是我们之前 _iob 的地址,现在 TA 指向的是 Hello 和 Kiya 中间的空格
然而输入的字符确实是从   0x0F5FDE00 开始存的,
我们可以猜测肯定是在格式化的过程中指针遇到了空格,函数就在  0x0F5FDE05 的地方 结束掉了,后面的不再处理



最终 str 中的内容:




那么现在我们可不可以利用 scanf 或者 scanf_s 获得一串带有空格的字符串呢?
现在已经知道所有输入的字符确实是在缓冲区中的,只是 scanf_s 的格式化功能将 TA 断掉了。

上代码:
#include<stdio.h>
int main(int argc,char* argv[])
{
	char str[20];
	scanf_s("%s",str,18);
	char* p = *(char**)((int)__iob_func()+8);
	printf_s(p);
	return 0;
}

这样我们就是利用了一下  __iob_func   函数,让他来为我们返回 _iob 的地址,
然而此时 _iob 已经指向了第一个空格的地方
然后将其强转成char类型的指针,就可以输出啦
可是为什么要加8呢?

(int)__iob_func()是将__iob_func()执行过后 TA 的返回值_iob 转成整形
其实有个小秘密,虽然我们输入过后 _iob 中的地址已经变成指向第一个空格处,但其实,起始地址就在 TA 的不远处~
(这个图是后来截的,地址可能和上面的不同)




如图,选中部分,就是第一个字符的地址,而上面的 0FA1DE05 则是空格的位置
所以我们将  ( int ) __iob_func ()  加上8个字节就得到了起始地址。

其实根据下图这个类型属性,也可以知道TA是个缓冲区,哈哈


结果:



又一炫酷技能 Get  √,哈哈哈



相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页