语法 fgets函数原理初探

来源:互联网 发布:java全栈工程师是什么 编辑:程序博客网 时间:2024/05/21 22:49

问题来源于《 c与指针》 1.7章节中的一个问题:

问题:下面的代码可能出现什么问题?

              

[html] view plaincopy
  1. while ( gets (input) != NULL){}  
首先我粗略的分析了一下,这行语句不就是输入一个字符串吗。 “!= NULL” 表示如果输入成功了,就继续运行。没想出来什么问题,于是决定在机器上面编译一下。于是写了代码如下:

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main()  
  4. {  
  5. char * input = NULL;  
  6. while (gets(input) != NULL){  
  7.   puts(input);  
  8. }  
  9. return EXIT_SUCCESS;  
  10. }  

编译运行结果如下:

[html] view plaincopy
  1. [root@localhost program]# gcc -g pgetsDemo.c -o pgetsDemo  
  2. /tmp/cc0OMzQL.o: In function `main':  
  3. /program/pgetsDemo.c:6: warning: the `gets' function is dangerous and should not be used.  
  4. [root@localhost program]# ./pgetsDemo   
  5. hello  
  6. Segmentation fault  

寻找解决办法途中得知:现在linux下使用fgets函数代替了gets函数,原因:

安全性问题

fgets函数原型为

char *fgets(char *s, int n, FILE *stream);
从stream所指的文件读入字符到s所指的内存空间中,直到读到换行符、文件尾或n-1个字符为止,最后会加上NULL 作为字符串结束,即s[n-1] = NULL;如果在未读到n - 1个字符时,读到了换行符或者 文件结束标志(EOF),那么就将换行符或者文件结束标志(EOF)都读到s中,此时,再在换行符或是文件结束标志(EOF)后面添加 NULL。

gets函数原型为 

char * gets(char s);

没有限制输入缓冲区的大小,容易造成溢出

从中容易得知,fgets函数比gets函数安全。


于是,修改源代码为:

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main()  
  4. {  
  5. char * input = NULL;  
  6. while (fgets(input,10,stdin) != NULL){  
  7.      puts(input);  
  8. }  
  9. return EXIT_SUCCESS;  
  10. }  

编译,运行:

[html] view plaincopy
  1. [root@localhost program]# gcc -g getsDemo.c -o getsDemo  
  2. [root@localhost program]# ./getsDemo  
  3. hello  
  4. Segmentation fault  

编译通过了但是还有还有 段错误的问题,使用GDB断点调试;提示:

Program received signal SIGSEGV, Segmentation fault.

此种错误一般是程序访问了不该访问的内存导致,内存设置了访问权限等等,

经查询,造成段错误的原因归纳如下(引用 cnblogs just_a_coder):

1.内存访问越界

 a) 由于使用错误的下标,导致数组访问越界

 b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符

 c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

2 多线程程序使用了线程不安全的函数。

3 多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

4 非法指针

a) 使用空指针

b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

5 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

经过分析:

[html] view plaincopy
  1. char * input = NULL;  
  2. while (fgets(input,10,stdin) != NULL)  
应该是这部分犯了以上的错误 4.b 随意使用指针转换,因为fgets函数在进行赋值的时候是一个字符一个字符的给的,就像下面这样:

char input[10];

输入 "hello"

赋值过程为:

[html] view plaincopy
  1. input[0] = 'h';  
  2. input[1] = 'e';  
  3. input[2] = 'l';  
  4. input[3] = 'l';  
  5. input[4] = 'o';  
  6. input[5] = NULL;  

而我上面是这样写的:

[html] view plaincopy
  1. char * input = NULL;  
  2. while (fgets(input,10,stdin) != NULL)  
运行流程也是:

[html] view plaincopy
  1. input[0] = 'h';  
  2. input[1] = 'e';  
  3. input[2] = 'l';  
  4. input[3] = 'l';  
  5. input[4] = 'o';  
  6. input[5] = NULL;  
但此时,input是一个字符指针,而不是一个数组首地址。

下面的问题就是指针能不能经过下标赋值,以下代码验证:

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main()  
  4. {  
  5.   
  6. char * p;  
  7. p[0] = 'a';  
  8. printf("%s",p);  
  9. return EXIT_SUCCESS;  
  10. }  
运行结果为:

[html] view plaincopy
  1. Segmentation fault  
可知,不能为指针下标赋值,(PS:如果赋值需要先开辟空间。)

OK,原因找到了.

正确的代码应该是这样的:

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main()  
  4. {  
  5. char input[10];  
  6. while (fgets(input,10,stdin) != NULL){  
  7.   puts(input);  
  8. }  
  9. return EXIT_SUCCESS;  
  10. }  

编译运行,输出为 

hello

hello


此程序稍加修改:

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main()  
  4. {  
  5. int i = 0;  
  6. char input[10];  
  7. while (fgets(input,10,stdin) != NULL){  
  8.   puts(input);  
  9.   i ++;  
  10. printf("i = %d\n",i);  
  11. }  
  12. return EXIT_SUCCESS;  
  13. }  

编译,运行看执行效果:

[html] view plaincopy
  1. [root@localhost program]# ./getsDemo  
  2. hello  
  3. hello  
  4.   
  5. i = 1  
  6. aaaaaaaaasssssssssddddddddfgggggghhhhh  
  7. aaaaaaaaa  
  8. i = 2  
  9. sssssssss  
  10. i = 3  
  11. ddddddddf  
  12. i = 4  
  13. gggggghhh  
  14. i = 5  
  15. hh  
  16.   
  17. i = 6  

当字符串较长时,fgets会分多次读入。
0 0
原创粉丝点击