当前位置: 代码迷 >> 综合 >> 01-0006 C++内存访问越界 [问题整理]
  详细解决方案

01-0006 C++内存访问越界 [问题整理]

热度:54   发布时间:2023-11-24 01:51:10.0

C++内存访问越界 [问题整理]

  • 1. 相关问题
  • 2. 出现内存访问越界或内存泄露可能的原因
    • 2.1 没有delete或者free
      • 2.1.1 直接不释放
      • 2.1.2 释放错误
      • 2.1.3 释放void*
    • 2.2 memset()导致的内存越界
    • 2.3 运行库的链接可能会导致内存越界
    • 2.4 其他情况
  • 3. 个人代码出现问题的原因
    • 3.1 描述
    • 3.2 忽略项

1. 相关问题

在尝试解决上述问题的时候,搜索的关键词如下:

  • ntdll.dll crash
  • VS未加载ntdll.pdb可能的错误原因
  • ntdll.pdb断点
  • C++ delete出错
  • C++动态数组delete出错
  • VS 触发断点,内存释放异常
  • C++".exe" 触发了一个断点
  • C++动态数组空间的释放
  • C++delete偶尔出错[rRelese模式]

2. 出现内存访问越界或内存泄露可能的原因

2.1 没有delete或者free

通过new或者malloc分配的内存空间没有及时的释放,或者释放不当造成的内存泄露。

2.1.1 直接不释放

函数体内申请内存,运行结束后不释放[也没有返回,提供其他位置释放的可能性]

void fun(){
    int* a=new int[10];
}

2.1.2 释放错误

释放不够彻底,delete使用方法错误[仅仅释放了0号元素或第一层指针]

void fun(){
    int* a=new int[10];delete a;//仅仅释放了0号元素
}void fun(){
    int** a=new int*[10];for(int i=0;i<10;i++){
    a[i]=new int[10];}delete [] a;//第二层的空间并没有释放,应该循环释放
}

如果在delete位置出现中断,是因为将要被delete的这个指针指向的空间发生了访问越界的情况,导致了堆被破坏,从而使得delete失败

2.1.3 释放void*

释放void* 导致析构函数不能被正常调用,这种情况有可能出现:

void fun(){
    void* pVoid = new int(2);delete pVoid;
}

备注:关于上述内存的释放,直接引用原作者写的一段文字说明,没有实际验证。其是否能够正确释放,与编译器所处的模式也有一定的关系,具体看引用的回复部分。

这时候内存会正确释放吗?我们知道指针是分类型,特定类型的指针寻址的字节数是不一样的,比如字符指针加一的时候是移动一个字节,int类型指 针加一的时候,移动两个字节。因为第一次申请的是int型的两个字节的内存,如果系统释放内存的时候是按类型释放的话,delete一个int型的指针能 正确释放int型指针所指向的内存,因为系统知道,int型就占两个字节,从指针的起始位置,释放两个字节的指针不就行了?但是,这样看来,第二段代码就 有问题:在第二段代码中,从堆中申请了一个int型的两个字节的内存赋给一个void型的指针,然后释放这个void类型的指针所指向的内存,由于 void类型指针是无类型的,系统无法知道void类型的指针指向多大的内存空间,这样,能正确释放指针所指向的内存空间吗?会不会造成内存泄露?为什么 呢?
.
这个时候我们就需要对变量的内存分配有一定的了解。
.
比如,int *pNum = new int(2);所申请的内存就是2个字节吗?答案是否定的,每当我们从堆里面申请一块的内存时,系统所分配的内存比对象所占用的内存要稍微大一点,就像 OSI协议中每层都要加一些特定的信息用于辨认一样,系统也会为内存加上一个标识头,用于标识这块内存的边界,这种技术成为cookie,不要以为 cookie是http协议中特有的标识信息,在c++中也有这种技术。其实cookie是一种很古老的技术,最初用来在进城间传递一些信息,后来因为用 于http协议中用来标示用户信息而为世人所熟知。扯远了,回到前面所说。所以当把一个int类型的指针赋给一个void类型的指针,然后用void类型 的指针释放这块内存,会正确释放,因为,类型只是给你看的,系统自有自己的一套标识方法。
.
回复:你说的这种情况只存在于debug,如果你用的是release,那就没有边界的

关于释放void*,最常见问题出现在类中,用void*指向类的对象,delete的时候不会调用析构函数导致内存的泄露,具体情况如下:

class A;
void fun(){
    void* ptr=new A();delete ptr;//A的析构函数不会被调用
}

原文地址:
C++造成内存泄漏的原因汇总
void*指针及delete释放void*内存(转)
释放void*指针 [推荐阅读]

2.2 memset()导致的内存越界

程序从堆中分配的内存使用完毕后必须显式释放,否则这块内存就不能被再次使用,即这块内存泄漏了。内存泄漏导致软件在运行过程中占用越来越多的内存,程序的效率会越来越低。

使用malloc、new等运算符动态分配的内存,必须使用free、delete显式释放;memset函数可能导致指向某块内存的指针的值发生了变化,从而导致释放内存失败,造成内存越界。

//以下代码会造成内存越界,但是编译不会报错,也有可能不会崩溃[崩溃偶尔会发生]int * x0 = new int[10];int * x1 = new int[10];int * x2 = new int[10];memset(x0, 0, 3 * 10 * sizeof(int));int * x3 = new int[10];

相关原文地址:
.exe 已触发了一个断点
memset和memcpy使用不当而引起的memory溢出
memset函数导致内存泄露的问题

2.3 运行库的链接可能会导致内存越界

msvcrt.lib,可能会导致内存越界,因为动态链接的库和静态链接的库可能会重载了new运算符,导致了不同的堆存在,可能会在一个堆上申请的空间在另一个堆上释放!
这里有两点需要注意:

  1. 尽量不要混合静态链接和动态链接,因为他们会申请不同的堆。
  2. 注意任何编译的警告,[warning LNK4098: 默认库“msvcrtd.lib”与其他库的使用冲突;请使用 /NODEFAULTLIB:library]

这里已经明确提示开发者可能会出现冲突问题了,所以应该解决掉他!
原文链接:C++中delete崩溃的问题

2.4 其他情况

以下几种情况属于个人写代码中出现过的问题,坑都踩过,需要记录:

  • 数组下标访问越界[自己写了某种神奇计算索引的方程导致索引越界 ]
  • strcpy等系列函数的使用,字符数组空间不够[计算size的问题 ]
  • delete之后不置位NULL,再次访问[需要养成良好习惯 ]
  • 使用了已经被销毁的函数返回的地址或者引用[确定自己使用的东西存在 ]
  • 指针指向临时对象,该对象在某语句块执行完毕之后被析构[指针指向空间不复存在 ]
  • 类型强转导致的访问越界[派生类指针指向积累对象 ]
    参考链接:基类指针指向派生类对象
  • 多线程访问共享对象时,可能该对象已经被某线程析构掉

原文地址:C++什么时候出现访问越界?

3. 个人代码出现问题的原因

3.1 描述

VS运行QT程序的过程中是不是会在delete[]的位置出现中断,没有中断信息的提示,仅仅显示一个叉号,有的时候会直接断开在QT main函数中的最后一句,甚至是会出现的ntdll.dll中,但是此时提示没有相关的pdb消息无法调试。

//以下几处出现中断
void fun(){
    ...delete[] tempPtr1;//maybe crash...delete[] tempPtr2;//maybe crash......delete[] tempPtr3;//maybe crash
}int main(){
    ...return w.exec();//maybe crash 
}

3.2 忽略项

Relese模式:我的VS目前是reles模式下,没有办法精确的定位出错的位置。

relese模式下,这种与内存有关的bug并不是100%的命中,因此bug并不是每次都会出现,更为奇葩的是,发生中断的位置并不一定是你写的可能产生数组越界的位置,此时,你可能一直在检查根本没有发生内存越界的那部分代码!。一定要调到Debug模式下,才能够更快的找到错误。


言:内存一定要处理妥当,要手动的,主动的,准确的进行释放,否则不仅仅会出现内存占用大的情况,还会出现访问越界导致程序崩溃,更可怕的是出现内存泄露,影响程序及其使用者的安全。