首先举个例子
#include<iostream>
using namespace std;
int main(){
char buf1[8];
gets(buf1);
printf("%s",buf1);
}
多数情况下,特别是我们在学习一门语言的时候,很难发现以上代码有什么问题。但是当我们把代码调整如下:
#include<iostream>
using namespace std;
int main(){
char buf1[8];
char buf2[3];
gets(buf2);//注意这里对buf2进行赋值
printf("%s\n",buf2);
printf("%s",buf1);
}
奇怪的事情发生了
我们输入了abcdef,只赋值给了buf2,针对buf1并没有任何操作,但是buf1还是输出了我们不想要的值
引入正题,原因是gets导致了内存操作不安全行为
我们将代码修改如下:
#include<iostream>
using namespace std;
int main(){
char buf1[8];
char buf2[3];
printf("%d\n",&buf2);
printf("%d\n",&buf1);
}
两个变量在内存中位置如下(此处buf2在buf1前面,所以我在前面针对buf2进行操作,否则,问题就展示不出来了):
我们在使用了gets函数后,如果输入的字符串数量大小小于3个时,不会发生问题,但是如果输入超过3个,比如“abcdef”,则内存赋值会如下:
所以,即使没有对buf1没有进行操作,但是gets在操作buf2的时候,如果输入数据过大,该函数不会检查是否合理,而是会直接将多余数据覆盖到其下游内存的区域,导致,内存数据发生不可预料的篡改,因此引发了不安全行为。
所以把这类会引发内存错误的行为称为不安全行为
所以我们在实际项目中最好避免使用gets不安全函数,而替换使用具有相同功能的安全函数fgets(当然在刷算法,练习码代码速度的时候,使用不安全函数一般没什么影响)
我们再把代码调整如下:
#include<iostream>
using namespace std;
int main(){
char buf1[8];
char buf2[3];
fgets(buf2,3,stdin);
printf("buf2:%s\n",buf2);
printf("buf1:%s",buf1);
}
使用安全函数后,即使输入参数值大于3,但是fgets会自动屏蔽多余值,而不会操作其邻近内存进行覆盖。(注意,此处buf2为“ab”,因为会自动在字符串末尾添加‘\0’空字符)
因此,可以看出安全函数fget要更安全一些。
最后说明一下:
安全函数需要指定输入字符串长度,如果在一些场合下,需要提前输入字符串长度,或者对输入参数长度提前有安全保障,则使用gets也是没有问题的。
除了gets/fgets函数外,c语言中还有其他一些经常使用的安全函数
scanf
#include<iostream>
using namespace std;
int main()
{
char euf[4];
char buf[4];
scanf("%s",&buf);
printf("buf:%s\n",buf);
printf("euf:%s\n",euf);
}
由于输入的数据数量大于4,同样造成了euf内存数据被篡改,引发不安全隐患
注意:
scanf函数有三个特点:
- 取数据时遇到空格、回车、TAB就会停止(截取之前数据);
- scanf函数和都是从输入流缓冲区中读取数据的,而不是从键盘(终端)缓冲区读取值的。读取时遇到回车\n即结束,且回车\n会被读入输入缓冲数据流中,这样第二次的读入函数将输入缓冲区中的回车\n读取走了,没有等待键盘的二次输入。
- scanf读取字符串时,会舍弃最后的回车符。
改用如下:
char buf[5]={’\0’};
scanf_s(“%s”,buf,5); //最多读取4个字符,因为buf[4]要放’\0’
//如果输入1234567890,则buf只会接受前4个字符
待补充: