gets与fgets,puts与fputs区别

一.gets与fgets
//gets函数很危险,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 函数一样。
二.puts与fputs的区别:
与 gets 函数一样,对于 puts 函数,同样建议使用 fputs 函数来代替 puts 函数。如下面的示例代码所示:
int main(void)
{
char buffer[11];
fgets(buffer,11,stdin);
fputs(buffer,stdout);
return 0;
}
其中,puts 函数的原型如下所示:
int puts(const char *str);

我们知道,puts 函数主要用于向标准输出设备(屏幕)写入字符串并换行,即自动写一个换行符(’\n’)到标准输出。理论上,该函数的作用与“printf("%s\n",str);”语句相同。但是,puts 函数只能输出字符串,不能进行相关的格式变换。与此同时,它需要遇到 null(’\0’) 字符才停止输出。因此,非字符串或无 null(’\0’) 字符的字符数组最好不要使用该函数打印,否则无法正常结束。如下面的代码所示:
int main(void)
{
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’};
puts(str);
return 0;
}
在上面的示例代码中,因为字符数组 str 在结尾处缺少一个 null(’\0’) 字符(也就是说它不是一个严格意义上的字符串)。因此,在调用 puts 函数的时候,程序将不知道什么时候停止输出,从而导致输出结果未定义。运行结果如下图所示:

图 1 示例代码的运行结果(Microsoft Visual Studio 2010)

正确的做法是应该在字符数组 str 的结尾处添加一个 null(’\0’) 字符,如下面的示例代码所示:
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’,’\0’};
fputs 函数的函数原型如下所示:
int fputs(const char *str, FILE *stream);

相对于 puts 函数,fputs 函数用来向指定的文件写入一个字符串(不换行)。当然,也可以使用 stdout 作为参数进行输出显示(它同样需要遇到 null(’\0’) 字符才停止输出),如下面的代码所示:
int main(void)
{
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’,’\0’};
fputs(str,stdout);
return 0;
}
其运行结果如下图所示:


图 2 示例代码的运行结果(Microsoft Visual Studio 2010)

当然,fputs 函数主要用于对指定文件进行写入操作,如下面的示例代码所示:
int main(void)
{
FILE *fp=NULL;
fp=fopen(“myfile.txt”,“wb”);
if(fp == NULL)
{
printf(“不能够访问该文件.\n”);
exit(1);
}
fputs(“this is a test”, fp);
fclose(fp);
fp=NULL;
return 0;
}
运行上面的示例代码,文件“myfile.txt”会被写入一行“this is a test”字符串。

与 fgetws 一样,C99 同样也提供了 fputs 函数的宽字符版本 fputws,其函数的一般原型如下面的代码所示:
int fputws(const wchar_t * restrict s, FILE * restrict stream);

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值