C++内存管理问题

本文主要是为了谈谈C++编程过程中内存的手动申请和释放问题,以及记录本人在释放内存时犯的错误。

首先先讲一下内存的分配和划分方式(转自https://www.cnblogs.com/ruixin-jia/p/5877492.html)

内存分配方式 
  内存分配方式有三种:

  [1]从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

  [2]在上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  [3]从上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

在编写程序是尽量避免大量地定义全局变量(一方面大量地全局变量会占用大量地计算机内存,另一方面,大量地定义全局变量会大致我们在变量名称的构思上耗费很多精力,毕竟不能重复嘛,而局部变量会由计算机自动释放内存,出了其作用域,变量名又可以被使用,方便很多)。

下面看一下,一段C/C++代码编译的程序具体的内存划分:

1、栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。

2、堆区(heap) — 在内存开辟另一块存储区域。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。c中用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上,C++中则用new和delete来分配和释放,当然C++中可以兼用C中的方式,而纯c风格的不可以使用new和delete,这部分处理不好就会造成内存泄漏

3、全局区(静态区)(static)—编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。而C++则没有这个区别 - 程序结束后由系统释放

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放,如string str = “Hello World”;中“Hello World”就存放于此

5、程序代码区—存放函数体的二进制代码。

下面根据我自己碰到的问题具体分析一下:

下面的程序是剑指offer中的例1(被说删掉了一部分)。

#include<cstring>
#include<cstdio>
#include<stdlib.h>
class CMyString
{
public:
   //构造函数中给了一个默认值,在实现中,没有参数,则默认使用改值
	CMyString(char* pData = nullptr);
	CMyString(const CMyString& str);//深拷贝构造函数

	~CMyString(void);

	CMyString& operator = (const CMyString& str);//运算符重载函数

	void Print();

private:
	char* m_pData;
};

CMyString::CMyString(char *pData)
{
	if (pData == nullptr)
	{
		m_pData = new char[1];
		m_pData[0] = '\0';
	}
	else
	{
		int length = strlen(pData);
		m_pData = new char[length + 1];
		strcpy(m_pData, pData);
	}
}

CMyString::CMyString(const CMyString &str)
{
	int length = strlen(str.m_pData);
	m_pData = new char[length + 1];
	strcpy(m_pData, str.m_pData);
}

CMyString::~CMyString()
{
	delete[] m_pData;
}

CMyString& CMyString::operator = (const CMyString& str)
{
	if (this == &str)
		return *this;

	delete[]m_pData;
	m_pData = nullptr;

	m_pData = new char[strlen(str.m_pData) + 1];
	strcpy(m_pData, str.m_pData);

	return *this;
}

// ====================测试代码====================
void CMyString::Print()
{
	printf("%s", m_pData);
}

void Test1()
{
	printf("Test1 begins:\n");

	char* text = "Hello world";

	CMyString str1(text);
	CMyString str2;
	str2 = str1;

	printf("The expected result is: %s.\n", text);

	printf("The actual result is: ");
	str2.Print();
	printf(".\n");
}

int main(int argc, char* argv[])
{
	Test1();
	system("pause");
	return 0;
}

以上程序的运行结果如下:


正确地输出了str1和str2的值,利用赋值运算符完成了两个实例的相互赋值操作。

      

     之后我就有一个疑问,就是拷贝函数不是可以直接将str1拷贝给str2吗,为什么还要多此一举用运算符重载?把重载函数删掉直接用拷贝函数行不行呢?于是我就将赋值运算符重载函数的声明(

CMyString& operator = (const CMyString& str);//运算符重载函数

)和定义删除,其它不改动的情况下运行了一遍,发现结果并不是很好,程序死了,运行结果如下:

虽然结果输出了,但程序不能终止,一直处于调试状态,寻找问题,发现是直接赋值造成的。

如果改成

结果正常输出:


为什么会这样?经过查找,得到以下结论:赋值重载和拷贝函数很像尤其是都使用“=”时,但拷贝构造函数实在实例化的时候才会被调用,如果实例化完之后再用“=”就不会调用拷贝构造函数了,此时就是运算符重载。

        然后我就想为什么将赋值重载函数删除后直接赋值就不行了,是应为C++中本来就不可以将两个实例进行互相赋值吗?显然不是的,虽然程序执行有问题,但通过结果可以看得出,str2是已经通过“=”获得了str1的所有数据了的。


       经过一通折腾,发现,只要我将析构函数中的delete []m_pData;删除程序就可以正常运行,但这样又会造成内存泄漏,显然不合适,但基本可以肯定是内存管理问题了。是str2 = str1这个操作将str2和str1的地址变得一样了?最后导致同一个地址的内存空间删除两次?于是查看各自的内存地址,发现并不一样,也就是说实例化后每个实例的内存地址是不一样的 。


     没办法,只好查看构造函数,拷贝构造函数和析构函数的运行情况(在每个函数下输出其函数名),问题出现了,构造函数正常运行,拷贝构造函数没有运行,析构函数只运行了一次(应该是两次str1和str2)。导致这个结果的原因应该是:str2在实例化的时候没有参数传入,并没有通过构造函数为其在堆中分配资源,CMyString str2; 而str1在实例化的时候传入了参数text,在堆中分配了资源,所以执行析构函数的时候,str1有空间释放,而str2无空间释放,此时强行释放会导致计算机不知道释放哪块内存,最终程序就一直



总结一下:这次的问主要涉及到拷贝构造函数与赋值运算符重载的区别,内存空间的管理,以及再出现类似bug后调试的方法。










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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值