休对故人思故国,且将新火试新茶。诗酒趁年华。
——《望江南·超然台作》【宋】苏轼
目录
上期的最后我们抛出了一个新的问题,那就是如果我们发现目标对象的内存已经不足但我们还是将资源对象拼接到目标对象那里,那么在目标内存那里的内存会有什么变化,会对我们的程序有什么危害。现在就让我们看一看:
正文:
我们来看一下代码实例:
#include<iostream>
using namespace std;
#include<string.h>
const int MAX=14;
int main()
{
char s[MAX]={0};
char n[]={"hello"};
chr m[]={"wello"};
char x[]={"only you can do it "}
//1
cout<<strcpy(s,n);
//2
cout<<strcpy(s,m,2);
//3
cout<<strcat(s,m);
//4
cout<<strcat(s,x);//新加入的实例
int unsigned len=strlen(s);
for(int i=0;i<strlen(s);++i)
{
cout<<s[i]<<endl;
}
return 0;
}
在昨天的代码中我加入了一个新的字符数组,然后将这个字符数组拼接到s数组中,然后输出这个数组。这个时候问题就出现了,我们可以看到在第三步结束时我们的数组s所剩的存储空间还有4个位,但是我们的资源数组(x)的长度远远超过4,所以这个时候就会出现一个问题,那就是数据溢出。我们要知道数据溢出之后就会向后填充溢出的部分,但是这个数组之后的空间存储了其他的数据。这就是说溢出的数据会替换掉下一个位置原本的数据。(比如:我们s数组之后存储的是数组a,那么s数组溢出的数据就会侵占原本a数组中元素的位置,导致a数组的存储内容出现问题。)
这个问题十分的严重,因为一不小心就会导致整个项目出现bug无法运行,尤其可怕的是这种溢出系统是不会报错的,也就是说会在我们不知道的情况下出错。如果是个巨大的项目,那么就是一个十分棘手的bug。
首先我们要明白出现问题的原因:
strcat这类的函数在C语言等编程语言中,用于将两个字符串连接起来,即将源字符串(src)拼接到目标字符串(dest)的末尾。然而,这类函数存在安全缺陷,主要原因包括:
1. 缓冲区溢出
- 根本原因:strcat函数没有检查目标字符串(dest)的空间是否足够以容纳源字符串(src)。如果dest所指向的缓冲区大小不足以容纳两个字符串连接后的结果,那么strcat会继续在dest的原始内存区域之后写入数据,直到src的末尾。这会导致缓冲区溢出,可能覆盖相邻内存区域的数据,引发程序崩溃或安全漏洞。
- 危害:缓冲区溢出是许多安全漏洞的根源,攻击者可以利用这一漏洞执行任意代码、提升权限或绕过安全机制。
2. 错误的字符串声明方式
- 在使用strcat时,如果目标字符串是通过指针指向一个字符串常量(如
char *dest = "initial";
),则尝试修改这个字符串(即拼接新内容)将导致未定义行为,因为字符串常量通常存储在只读内存区域。 - 正确的做法是使用字符数组(如
char dest[SIZE] = "initial";
)来确保有足够的空间进行字符串操作,并且该数组是可修改的。
3. 缺乏对NULL指针的检查
- 如果strcat的任一参数为NULL,其行为是未定义的。在实际使用中,应该检查指针的有效性,以避免潜在的空指针解引用错误。(主要原因)
解决方案:
所以C++有了这类函数的升级版,更加的安全:
strnlen_s,strcpy_s,strncpy_s,strcat_s等几个函数
请看优化后的代码:
/*#include<iostream>
using namespace std;
#include<string.h>
#include<cstring>
const int MAX=12;
int main()
{
char s[MAX]={0};
char n[]={"hello"};
char m[]={"wello"};
//cout<<strcpy(s,n);
cout<<strcpy_s(s,MAX,n) << endl;
//cout<<strcpy(s,n,2);
cout<<strcpy_s(s,MAX,n,2);
//cout<<strcat(s,m);
cout<<strcat_s(s,MAX,m);
unsigned int len=strlen(s);
for(int i=0;i<len;++i)
{
cout<<s[i]<<endl;
}
return 0;
}
*/
#include<iostream>
using namespace std;
#include<string.h>
#include<cstring>
const int MAX=12;
int main()
{
char s[MAX]={0};
char n[]={"hello"};
char m[]={"wello"};
// 使用strncpy来安全地复制字符串
strncpy(s, n, MAX);
s[MAX-1] = '\0'; // 确保字符串以null终止
cout << s << endl;
// 使用strncat来安全地连接字符串
strncat(s, m, MAX-strlen(s)-1);//求出剩余内存的长度
s[MAX-1] = '\0'; // 确保字符串以null终止
cout << s << endl;
unsigned int len = strlen(s);
for(unsigned int i=0; i<len; ++i)
{
cout << s[i] << endl;
}
return 0;
}
一些第三方库提供了额外的安全函数,这些函数通过检查边界和提供其他保护措施来增强C的安全性。例如,Microsoft的C运行时库(CRT)提供了安全版本的函数,如
strcpy_s
。(部分编译器可能无法使用)
这里说明一下,代码中其实我列了两种的方式,第一种可能会在编译器中无法通过,所以我又写了第二种方式,这种方式也是可以的,不过就是要自己对结尾加上一个结束符,这就考验编写者的个人细心程度了,所以这种办法也不是很常用,因此在C++中又诞生了一种更简单的,更安全的方法那就是string类里的函数(比如:strlcpy)。
这些代码很简单没什么要特殊注意的,自己记住这些格式就没问题,只需要记住最原始的strcpy之类的函数是没有自动停止的功能,会产生一些隐藏的bug,有这个意识就可以了,即使对此有了完整的技术可以杜绝这种隐患,但是我们还是要保持这种意识,对我们写代码有帮助。
下期我们就来讲一下string类里的相关操作。
🆗到这里,这篇关于:C++字符串操作中的陷阱就说完了,求一个免费的赞,感谢阅读。