调试崩溃和内存泄露

本文详细介绍了内存泄露的原因,并在最后一节介绍了使用gdb调试核心转储的方法来定位错误原因。

DEBUGGING CRASH & MEMORY LEAK

原文链接https://www.bogotobogo.com/cplusplus/CppCrashDebuggingMemoryLeak.php

Table of Contents

一、未定义行为:Undefined Behavior

二、内存使用错误和内存损坏

三、访问空指针-无效对象

四、悬空指针

五、未初始化指针

六、解除分配错误

七、不调用派生类析构函数

八、缓存溢出

九、gdb调试段错误生成的核文件


 

一、未定义行为:Undefined Behavior

未定义行为可能会发生,因为有很多事情没有被C/C++标准所规定,原因有很多。

原因之一可能是允许灵活性的实施。该规范将某些操作的结果定义为未定义。当我们面对不确定的行为时,任何事情都可能发生,比如崩溃、冻结等等。最糟糕的情况是它似乎正常工作。

例如,与数组绑定检查相关,该标准只说明如果我们访问数组边界内的元素应该发生什么。行为未定义。当我们使用freedelete来释放以前由new分配的对象的内存时,任何事情都可能发生。 同样,在未初始化的内存中使用这些值也会导致不可预知的程序行为。

二、内存使用错误和内存损坏

以下是内存相关问题的主要来源。

  1. 使用未初始化的内存
  2. 使用我们不拥有的内存
  3. 使用比分配更多的内存(缓冲区溢出)
  4. 使用错误堆内存管理

三、访问空指针-无效对象

当我们试图使用一个空指针访问一个对象的方法时,我们的程序崩溃了。这里是访问具有无效指针的对象的典型示例。

#include <iostream>

using namespace std;

class A 
{
	int value;
public:
	void dumb() const {cout << "dumb()\n";}
	void set(int x) {cout << "set()\n"; value=x;}
	int get() const {cout << "get()\n"; return value;}
};

int main()
{
	A *pA1 = new A;
	A *pA2 = NULL;
   
	pA1->dumb();
	pA1->set(10);
	pA1->get();
	pA2->dumb();
	pA2->set(20);
	pA2->get();

        return 0;
}

运行输出:

dumb()
set()
get()
dumb()
set()

我们有A类的三个成员函数,dumb()", "set()", 和 "get()" 。 指向A对象的指针正在调用A的方法。 用正确分配的指针pA1调用这些方法是没有问题的。但是,代码在这行崩溃了:

pA2->set(20); 

为什么?

在该行中,对NULL pA2调用“set(20)”,当我们试图访问A类的成员变量时,它将崩溃,而用指向A对象的相同NULL指针调用“dumb()”没有问题。

调用非法对象指针的方法与传递函数的非法指针相同。当在调用方法中访问任何成员变量时发生崩溃。换句话说,“set(20)”试图访问成员变量“value”,但“dumb()”方法不访问。

四、悬空指针

如果指针是悬空指针(指向已经释放的内存),或者指向当前堆栈或堆边界之外的内存位置,则它指向的是程序当前不拥有的内存。使用这种指针通常会导致程序崩溃。

当一个代码在释放了内存资源后,就会出现一个悬空指针,如下面的例子所示。

struct X 
{
	int data;
};

int foo() 
{
	struct X *pX;
	pX = (struct X *) malloc(sizeof (struct X));
	pX->data = 10;	   
	free(pX);
        ...	  
	return pX->data;
}

函数“foo()”通过使用已经释放其内存的指针“pX”返回结构体X的成员。pX指针的内存块很可能已经被用不同的值覆盖了。在最坏的情况下,它可能会深入到其他地方,直到它出现一些症状。 悬空指针是C/C++程序的一个常见的令人头疼的事。

五、未初始化指针

另一个常见错误是尝试访问未初始化的内存,如下所示。

void fooA()
{
	int *p;
	*p = 100;
}

大多数编译器的实现触发了“分段违反”。

作为另一个例子,下面的代码试图释放尚未初始化的指针“p”。

void fooB()
{
	int *p;
	free(p);
}

这个错误的结果实际上是未定义的,换句话说,任何事情都可能发生。

六、解除分配错误

释放已经释放的内存是内存错误的另一个例子。

void fooA() 
{
	char *p;
	p = (char *)malloc(100);	 
	cout << "free(p)\n";
	free(p);
	cout << "free(p)\n";
	free(p);
}

这种类型的错误导致未定义的行为,它可能崩溃或可能被忽略。

七、不调用派生类析构函数

ParentClass *pObj = new ChildClass;
...
delete pObj;

在上面的示例中,代码的意图是释放为子类对象分配的内存。但是,由于“pObj”的类型是指向Parent类的指针,因此它删除Parent对象,而保留为Child对象分配的内存不变。因此,内存泄漏。 

在这种情况下,我们需要使用一个虚析构函数来避免这个问题。调用~ParentClass(),然后在运行时调用Child类的析构函数~ChildClass(),因为它是一个虚析构函数。如果它没有被声明为virtual,那么只调用~ParentClass(),从ChildClass中留下任何分配的内存以持久化并泄漏。 

八、缓存溢出

根据字符串的长度,它可能试图在不分配内存的地方写入(void*memcpy(void* const void*source,size_t sz)。

char *s = (char *)malloc(128*sizeof(char));
memcpy(s, str, str_len);

作为另一个例子,当我们试图复制一个字符串时,我们需要考虑字符串结尾的空字符。

char *p = (char *)malloc(strlen(str));
strcpy(p, str);

在代码中,我们需要将strlen(str)改为 strlen(str)+1.。In the code, we need to change the strlen(str) to strlen(str)+1。

九、gdb调试段错误生成的核文件

当程序试图访问不允许的内存时,会发生段错误。这通常是由于源代码中指针的用法不当,取消了一个空指针,如下面的例子所示。这通常是由于源代码中指针的用法不当,取消了一个空指针,如下面的例子所示。

(段错误)通常是信号SIGSEGV集,它是在头文件信号signal.h文件中定义的。程序接收SIGSEV时的默认动作是异常终止。该操作将结束进程,但是会生成一个核心文件(也称为核心转储)来帮助调试,或者执行一些其他与平台相关的操作。核心转储是计算机程序工作存储器在特定时间记录的状态,通常当程序已经异常终止时。  

这里是生成core文件的文件。代码是在bad()中取消一个NULL指针,当我们运行时,它给我们提供了core文件。

/* bad.c */

int bad(int *pt)
{
  int x = *pt;
  return x;
}

int main()
{
  int *ptr = 0;   /* null pointer */
  return bad(ptr);
}
编译和运行:
$ gcc -g -o ./bad bad.c
$ ./bad
Segmentation fault
$ ls
bad  bad.c

正如我们从列表中看到的,core没有生成。要生成core文件,我们需要指定文件的大小

$ ulimit -c
0
$ ulimit -c unlimited
$ ulimit -c
unlimited
我们再运行一次:
$ ./bad
Segmentation fault (core dumped)
$ ls
bad  bad.c  core.2333

现在,让我们以exec和core文件名作为参数运行gdb:

$ gdb ./bad core.2333
GNU gdb (GDB) Fedora (7.5.1-37.fc18)
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/KHong/Work/Debug/bad...done.
[New LWP 2333]
Core was generated by `./bad'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004004f8 in bad (pt=0x0) at bad.c:5
5	  int x = *pt;
(gdb) backtrace
#0  0x00000000004004f8 in bad (pt=0x0) at bad.c:5
#1  0x000000000040051e in main () at bad.c:12
(gdb) 

更多关于核心转储的内容,请访问:  Debugging - core/memory dump

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值