C++调试内存泄漏问题总结

提示:本文是在学习完模板后,编写可变长度数组时,调试出现的问题,问题集中在内存泄漏上,可供大家学习借鉴。

文章目录

  • 基本的思路及程序功能
  • 一、源程序
  • 二、部分重要的头文件
  • 三、调试出现的问题及原因分析
  • 四、如何修改
  • 总结


基本的思路及程序功能

1、数组是一些连续的内存空间上存放了一些相同数据类型的一种“容器”,为了实现代码的可重复使用,可以借助模板来完成自定义的数组类。
2、同时,使用尾部插入数据时,为了提高空间使用效率,不需要每次插入一个元素时,就在堆区重新new出old_length+1的内存空间,而采用了直接new出32个字节长度的内存,用来存放数据,待下次数组空间不足以添加新元素时,重新new32*2个字节…以此类推。
3、还需要支持在数组中存放你自己定义的类,比如我定义了一个Person类,我想把它们存放在自己定义的数组中,完成班级信息汇总…等等功能。


提示:以下是本篇文章正文内容,代码执行在AMD-win环境下的Microsoft Visual Studio19社区版

一、源程序

.cpp代码如下:

#include
#include"MyArray.hpp"  //.hpp(理解为.h和.cpp的结合)是模板类在分文件中使用的标志!
using namespace std;

class Person
{
	friend ostream& operator<<(ostream& ost, Person& p);
private:
	int m_age;
	string m_name;
public:
	Person() {}; //默认构造函数,在new T[]时起到实例化对象的作用
	Person(string name, int age);
};
Person::Person(string name, int age)
{
	this->m_age = age;
	this->m_name = name;
}
ostream& operator<<(ostream& ost, Person& p)  //由于姓名和年龄是私有属性,需要将其声明为类Person的友元
{
	ost << "姓名:" << p.m_name << " " << "年龄:" << p.m_age << endl;
	return ost;  //此处返回this会报错,因为静态函数内部不允许使用this
}

template<class T>
void PrintArray(CArray<T>& arr)
{
	if (arr.length() == 0)
	{
		cout << "当前数组中不存在元素!" << endl;
		return;
	}
	for (int i = 0; i < arr.length(); ++i)
		cout << arr[i] << " ";
	cout << endl;
}

//已有数据类型测试
void test01()
{
	CArray<float> a1;  //开始的数组是空的    使用了模板之后,就一定要随身携带着<>,也就是参数列表!
	a1.pop_back();
	for (float i = 1.2; i < 5.1; i = i + 0.5)
		a1.push_back(i);  
	a1.push_back(271);
	a1.pop_back();  //从尾部删除一个元素
	a1.pop_back();
	a1.push_back(71);
	CArray<float> a2;
	a2 = a1;  //使用了赋值运算符的重载  深拷贝
	PrintArray(a2);
}

//自己编写的数据类型测试
void test02()
{
	Person p1("小明", 18);
	Person p2("小华", 16);
	Person p3("小亮", 14);
	Person p4("小光", 12);
	Person p5("小壮", 10);
	CArray<Person>my_class;
	my_class.push_back(p1);
	my_class.push_back(p2);
	my_class.push_back(p3);
	my_class.push_back(p4);
	my_class.push_back(p5);
	PrintArray(my_class);
	my_class.pop_back();
	PrintArray(my_class);
}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

二、部分重要的头文件

.hpp代码如下:

#pragma once
#include
#include
using namespace std;

const int byte_len = 32;  //byte_len定义,是push_back函数中一次添加的内存空间长度

template <class T>
class CArray {
private:
	int size;  //数组中元素的个数
	int count;  //在尾部添加元素时,用于计数
	T* ptr;  //指向动态分配的数组
public:
	CArray(int s = 0);  //有参构造函数 s表示数组元素个数,默认为0
	CArray(CArray& a);  //拷贝构造函数  深拷贝
	~CArray();
	void push_back(const T& v);  //在数组尾部添加一个元素v
	void pop_back(); //删除数组尾部的一个元素
	CArray& operator=(const CArray& a);  //用于对象之间的赋值
	int length()const { return size; }  //常量成员函数
	T& operator[](int i) { return ptr[i]; }  //用于支持根据下标访问数组元素,如a[i]=1;1=a[i];
};

//中间对于部分成员函数进行了省略,仅保存了重要的几个函数

template <class T>
void CArray<T>::push_back(const T& v)
{
	if (ptr)
	{
		if (count == 0 || count * byte_len == size)
		{
			T* tmp_ptr = new T[++count * byte_len];  //一次性重新分配32个空间,提高效率
			memcpy(tmp_ptr, ptr, sizeof(T*) * size);  //复制原数组内容  
			delete[]ptr;
			ptr = tmp_ptr;
		}
	}
	else  //原来数组是空的
		ptr = new T[1];
	ptr[size++] = v;  //加入新的数组元素
}


三、调试出现的问题及原因分析

在刚开始的程序版本中,运行会出现一下结果:
C++调试内存泄漏问题总结_第1张图片

可见,在所有内容均执行完毕后,程序提示引发了异常。
通过在网上查阅这个异常的代号,发现可能有如下四种原因:
1.空指针赋值
2.数组或者指针越界
3.指针没有初始化
4.访问已经析构的类的成员变量
经过断点运行,排查,最终认为错误出现在.hpp文件下push_back()函数中用到的memcpy()函数上。

原来的mencpy()部分代码如下:

代码如下(示例):

memcpy(tmp_ptr, ptr, sizeof(T) * size);

该处是调试出错的部分代码展示。

此时T代表的是Person类,sizeof(T)的大小是32字节…至于为啥sizeof(Person)就是32,原因是:C++中class的大小仅看非静态的成员变量(不考虑虚函数之类的),成员函数由于独占一份所以不占用类的空间,而C++在VS编译器下的string占用字节达到了28字节,再加上int的4字节,就是32个字节了。
C++调试内存泄漏问题总结_第2张图片

因此可以知道:
C++调试内存泄漏问题总结_第3张图片

四、如何修改

把代码改成下面这样子就可以了:

memcpy(tmp_ptr, ptr, sizeof(T*) * size);

因为指针长度恒定为4,这样子就可以保证复制的字节数一致,使用时就不会出错了!


总结

感觉自己就是太菜了,一个小小的问题居然花了这么久才解决…感觉还是要多用手啊,不能闭门造车和眼高手低。

完整代码详见我的C++专栏。

你可能感兴趣的:(C++学习,c++)