gets和fgets函数及其区别,C语言gets和fgets函数详解

每当讨论 gets 函数时,大家不由自主地就会想起 1988 年的“互联网蠕虫”,它在 UNIX 操作系统的 finger 后台程序中使用一个 gets 调用作为它的攻击方式之一。很显然,对蠕虫病毒的实现来说, gets 函数的功劳不可小视。不仅如此,GCC 也不推荐使用gets和puts函数。

那么,究竟是什么原因导致 gets 函数这么不招人待见呢?

我们知道,对于 gets 函数,它的任务是从 stdin 流中读取字符串,直至接收到换行符或 EOF 时停止,并将读取的结果存放在 buffer 指针所指向的字符数组中。这里需要注意的是,换行符不作为读取串的内容,读取的换行符被转换为 null('\0') 值,并由此来结束字符串。即换行符会被丢弃,然后在末尾添加 null('\0') 字符。其函数的原型如下:
char* gets(char* buffer);
如果读入成功,则返回与参数 buffer 相同的指针;如果读入过程中遇到 EOF 或发生错误,返回 NULL 指针。因此,在遇到返回值为 NULL 的情况,要用 ferror 或 feof 函数检查是发生错误还是遇到 EOF。

函数 gets 可以无限读取,不会判断上限,所以程序员应该确保 buffer 的空间足够大,以便在执行读操作时不发生溢出。也就是说,gets 函数并不检查缓冲区 buffer 的空间大小,事实上它也无法检查缓冲区的空间。

如果函数的调用者提供了一个指向堆栈的指针,并且 gets 函数读入的字符数量超过了缓冲区的空间(即发生溢出),gets 函数会将多出来的字符继续写入堆栈中,这样就覆盖了堆栈中原来的内容,破坏一个或多个不相关变量的值。如下面的示例代码所示:
int main(void)
{
    char buffer[11];
    gets(buffer);
    printf("输出: %s\n",buffer);
    return 0;
}
示例代码的运行结果为:
aaa
输出: aaa

根据运行结果,当用户在键盘上输入的字符个数大于缓冲区 buffer 的最大界限时,gets 函数也不会对其进行任何检查,因此我们可以将恶意代码多出来的数据写入堆栈。由此可见,gets 函数是极其不安全的,可能成为病毒的入口,因为 gets 函数没有限制输入的字符串长度。所以我们应该使用 fgets 函数来替换 gets 函数,实际上这也是大多程序员所推荐的做法。

相对于 gets 函数,fgets 函数最大的改进就是能够读取指定大小的数据,从而避免 gets 函数从 stdin 接收字符串而不检查它所复制的缓冲区空间大小导致的缓存溢出问题。当然,fgets 函数主要是为文件 I/O 而设计的(注意,不能用 fgets 函数读取二进制文件,因为 fgets 函数会把二进制文件当成文本文件来处理,这势必会产生乱码等不必要的麻烦)。其中,fgets 函数的原型如下:
char *fgets(char *buf, int bufsize, FILE *stream);
该函数的第二个参数 bufsize 用来指示最大读入字符数。如果这个参数值为 n,那么 fgets 函数就会读取最多 n-1 个字符或者读完一个换行符为止,在这两者之中,最先满足的那个条件用于结束输入。

与 gets 函数不同的是,如果 fgets 函数读到换行符,就会把它存储到字符串中,而不是像 gets 函数那样丢弃它。即给定参数 n,fgets 函数只能读取 n-1 个字符(包括换行符)。如果有一行超过 n-1 个字符,那么 fgets 函数将返回一个不完整的行(只读取该行的前 n-1 个字符)。但是,缓冲区总是以 null('\0') 字符结尾,对 fgets 函数的下一次调用会继续读取该行。

也就是说,每次调用时,fgets 函数都会把缓冲区的最后一个字符设为 null('\0'),这意味着最后一个字符不能用来存放需要的数据。所以如果某一行含有 size 个字符(包括换行符),要想把这行读入缓冲区,要把参数 n 设为 size+1,即多留一个位置存储 null('\0')。

最后,它还需要第 3 个参数来说明读取哪个文件。如果是从键盘上读入数据,可以使用 stdin 作为该参数,如下面的代码所示:
int main(void)
{
    char buffer[11];
    fgets(buffer,11,stdin);
    printf("输出: %s\n",buffer);
    return 0;
}
对于上面的示例代码,如果输入的字符串小于或等于 10 个字符,那么程序将完整地输出结果;如果输入的字符串大于 10 个字符,那么程序将截断输入的字符串,最后只输出前 10 个字符。示例代码运行结果为:

aaaaaaaaaaaaaaaa
输出: aaaaaaaaaa

除此之外,C99 还提供了 fgets 函数的宽字符版本 fgetws 函数,其函数的一般原型如下面的代码所示:
wchar_t *fgetws(wchar_t * restrict s, int n, FILE * restrict stream);
该函数的功能与 fgets 函数一样。

推荐文章
2019 前端面试题汇总(主要为 Vue)

刚来3天,面试了几家公司,有些规模比较小,有些是创业公司,也有些已经发展的不错了;今天把最近的面试题目做个汇总,也给自己复个盘,由于我的技术栈主要为Vue,所以大部分题目都是Vue开发相关的。(推荐免

vulkanrt是什么软件

VulkanRT是一款免费开放的、跨平台的2D和3D绘图应用程序接口(API)软件。拥有标准规范文档、驱动程序、SDK开发包、符合性测试,甚至是测试版的游戏软件支持,一整套解决方案。VulkanRT官

vi按段落移动光标

在vi编辑器中,一个段落被定义为是以一个空白行开始和结束的片段。按段落移动光标的命令有以下两种。 {命令:该命令将光标向前移至上一个段落的开头。 }命令:该命令将光标向后移至下一个段落的开头。

C++ cin.getline用法详解

使用C++字符数组与使用string对象还有另一种不同的方式,就是在处理它们时必须使用不同的函数集。例如,要读取一行输入,必须使用cin.getline而不是getline函数。这两个的名字看起来很像

Linux info命令:info格式的命令帮助指令

info命令也可以获取命令的帮助。和man命令不同的是,info命令的帮助信息是一套完整的资料,每个单独命令的帮助信息只是这套完整资料中的某一个小章节。大家可以把info帮助信息看成一部独立的电子书,

UE4是什么?虚幻4引擎是什么?

UE4的全名是UnrealEngine4,中文译为“虚幻引擎4”。UE4是一款由EpicGames公司开发的开源、商业收费、学习免费的游戏引擎。 从1998年发行至今,UE4一共经历了UE、UE2、

JS创建对象(3种方式)

在JavaScript中创建对象的方式有3种,简单介绍如下。 构造对象 使用new运算符调用构造函数,可以构造一个实例对象。具体用法如下: varobjectName=newfunctionNa

什么是Vim,Vim及其安装

通过前面的学习我们知道,Linux系统中“一切皆文件”,因此当我们在命令行下更改文件内容时,不可避免地要用到文本编辑器。 作为一名Linux初学者,你必须熟练掌握Linux中至少一款文本编辑器的用法

C++ STL基本组成(6大组件+13个头文件)

通常认为,STL是由容器、算法、迭代器、函数对象、适配器、内存分配器这6部分构成,其中后面4部分是为前2部分服务的,它们各自的含义如表1所示。 表1STL组成结构 STL的组成 含义

Linux远程管理工具(PuTTY和SecureCRT)

通过《Linux远程管理协议》一节可以知道,Linux远程管理服务器多基于SSH协议。本节给大家介绍2种常见的基于SSH协议的远程管理工具,分别是PuTTY和SecureCRT。 在使用远程管理工具

JS this指针深度剖析

JavaScript函数被调用后会在一个特定的运行环境内执行,这个运行环境就是函数的调用者,或者说是调用函数的对象。如果函数没有调用者(不是通过对象调用,而是直接调用),那么运行环境就是全局对象win

PHP break:跳出循环

break关键字可以使程序跳出当前的循环,可以在switch、for、while和dowhile 等语句中使用,这样可以终止循环体的代码并立即跳出当前的循环,执行循环之后的代码。 break关键字的

C++ STL priority_queue容器适配器详解

priority_queue容器适配器模拟的也是队列这种存储结构,即使用此容器适配器存储元素只能“从一端进(称为队尾),从另一端出(称为队头)”,且每次只能访问priority_queue中位于队头的

MyBatis一对一关联查询(级联查询)

一对一级联关系在现实生活中是十分常见的,例如一个大学生只有一张一卡通,一张一卡通只属于一个学生。再如人与身份证的关系也是一对一的级联关系。 MyBatis如何处理一对一级联查询呢?在MyBatis中

JavaScrip常见面试题汇总(含答案)

一、请解释JavaScript中this是如何工作的。首先:this永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。匿名函数或不处于任何对象中的函数指向window。1、方法调用模式当函数

Python __iter__和__reversed__:实现迭代器

前面介绍了使用for循环遍历列表、元组和字典等,这些对象都是可迭代的,因此它们都属于迭代器。 如果开发者需要实现迭代器,只要实现如下两个方法即可: __iter__(self):该方法返回一个迭

SEO关键词的分类

只有明确关键词的分类方式之后,才能够根据网站想要达成的具体目的来筛选优质、合适的关键词,并根据关键词的优化难易程度将它们部署到网站的各个页面。 对关键词的分类可以有多种形式,每一种形式都对网站SEO

vi屏幕滚动命令(滚屏命令)

如果文件太大,一个屏幕不能将其内容完全显示出来,vi编辑器会采用分屏显示的方法。使用屏幕命令可以以屏幕为单位移动光标,方便地完成文件的滚屏和分页。 需要注意的是,屏幕命令不是光标移动命令,不能作为文

使用隐藏框架实现异步交互

隐藏框架只是异步交互的载体,它仅负责信息的传输,而交互的核心应该是一种信息处理机制,这样处理机制就是回调函数。 所谓回调函数,就是客户端页面中的一个普通函数,但是该函数是在服务器端被调用,并负责处理

C++ includes(STL includes)算法详解

includes()算法可以比较两个元素的集合,如果第一个集合中的全部元素都来自第二个集合,它会返回true。如果第二个集合是空的集合,它也返回true。下面是一些示例: std::setword