先说可能的情况:
- 向char* 多写入了超过其长度的字节。例如使用strcpy时,分配了n个元素,但源字符串长度也是n(实际复制时其实是n+1);
- 变量更改造成指针变换,如自加自减操作;
- 重复delete。
2,3都比较容易发现,一般不会错。
我们来看1。
引入
假设有以下代码片段:
char* a = new char[5];
strcpy(a,"AAAAA");
std::cout<<a<<std::endl;
delete[] a;
我的环境是Windows 11 + VSCode + gcc version 13.2.0 (x86_64-win32-seh-rev1, Built by MinGW-Builds project)以及VS2022。
以上这段代码编译能够通过,也能够执行。但在VS/VSCode的Debug模式下会报错。
VSCode会在strcpy处给出下划线并提示:
‘void* __builtin_memcpy(void*, const void*, long long unsigned int)’ writing 6 bytes into a region of size 5 overflows the destination [-Wstringop-overflow=]
向区域大小为5的区域写入6个字节会溢出目标
VS也会在执行时报错:
即:CRT检测到应用向对缓存末端后方写入
以上种种都说明我们向 a 中写入了超过5个字节。
原因
我们将目光投向 strcpy
strcpy简单复现一下是这样的:
char* strcpy(char* dst, char* src){
char* ret = dest;
while(*src != '\0'){
*dst = *src;
dst++; //向后移一位
src++;
}
*dst = *src;
return ret;
}
参考:https://blog.csdn.net/weixin_45031801/article/details/136616281
我们发现,在循环退出后,依旧有一行
*dst = *src;
及:将源字符串末尾的 ‘\0’ 字符复制到目标字符串末尾,也就是说,我们末尾还需要有一位来存储结束符 ‘\0’。
其次,从上述代码来看,即便src长度超过dst,复制也不会停止,因此strcpy是能够将超过dst分配的内存的数据写入dst的。
再来看delete。
参考:https://blog.csdn.net/qq_45657288/article/details/114699235
个人理解如下,仅供参考:
new 分配了一块对应类型大小的内存并返回指向这块内存的指针;delete 则通过指针释放指针指向的被占用的一块块内存。
同理,new [] 分配了一串连续的内存,返回这些内存的头部指针,并记录内存的数量信息;delete[] 则通过头部指针与 new [] 记录的信息逐个删除分配的内存。
在开始的例子中,new char[5] 分配了五块内存用于存储5个char,但在strcpy中,我们将看似只有5个字符实际有6个字符的字符串 “AAAAA\0” 复制给了a,造成了a与实际大小不符的情况,而正是这种情况造成了内存泄漏。也正是因此才有了以上的报错。这种报错由于只出现在Debug中,如果直接生成运行而又没有发现可能造成很严重的内存泄漏。