C/C++内存管理

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++内存管理相关的知识,希望对你有一定的参考价值。

C/C++内存分布

示例图:
在这里插入图片描述

栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
数据段:存放的是全局数据、与静态数据,全局数据又可分为已初始化的全局区(data)和未初始化的全局区(BSS);
代码段:可执行代码和只读常量(文字常量区);
内存映射段:文件映射、动态库、匿名映射

C语言内存开辟(堆上)的三大函数

面试题:malloc、calloc、realloc区别

三个函数的声明分别是:
void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);
相同点:都在stdlib.h函数库内它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL

**malloc用于申请一段新的地址,参数size为需要内存空间的长度,如:
char
p;
p=(char
)malloc(20);

calloc与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
char* p;
p=(char*)calloc(20,sizeof(char));

realloc是给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度
例如:
char* p;
p=(char*)malloc(sizeof(char)20);
p=(char
)realloc(p,sizeof(char)*40);

注意,这里的空间长度都是以字节为单位。

C语言的标准内存分配函数:malloc,calloc,realloc,free等。
malloc与calloc的区别为1块与n块的区别:
malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。
calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。
realloc调用形式为(类型*)realloc(ptr,size):将ptr内存大小增大(减少)到size。类似于调整,可调大也可调小。
free的调用形式为free(void
ptr):释放ptr所指向的一块内存空间。

探究realloc底层实现。

realloc()函数指的是在原有内存的基础上进行操作,对该内存的大小进行扩容或者是减容,底层实现:
只是从代码层面,没有从底层说

void* my_realloc(void* des, size_t size)
{
	void* new_space = malloc(size);//1.申请空间
	memcpy(new_space, des, size);//2.对原空间size大小个数据复制,此时size为新开辟的空间大小
	free(des);//3释放原空间
	des = NULL;
	return new_space;//返回新开辟的空间


}

realloc(void*des,size_t size)会做四件事:
1.根据size申请一段新的空间
2.对原空间的内容拷贝原空间size个字节大小。(假如说缩容的话,则可能会造成数据丢失).
3.释放掉原空间(这点很关键),这块的realloc会有两种处理策略,一个是在原空间上进行操作(扩容或者减容)不去寻找新空间 ,第二种情况是在新空间中开辟一段空间。(因为这是操作系统做决定的,所以我们无法模拟出来)。
4.在开辟成功的基础上返回新开辟的空间首地址。

void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

看一下这个需要free(p2)吗?答案是不需要的,因为根据上面那个realloc底层实现上来看,他会在封装realloc内部进行操作,free()掉原空间,所以此时入去再去free()的话就相当于对一块原空间free了两次(以上陈述建立在新空间中开辟)。

C++内存开辟与释放函数

内存开辟函数new与new[]操作符

new

new的底层实现:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}

new什么时候会开辟失败?
内存不足的时候会开辟失败,此时它会进入上面的那个if(_callnewh(size)==0)这个判断语句,然后对内存中的资源进行回收,若回收到了的话分配,回收不到的话会抛出 bad_alloc类型异常
new的使用:

void test()
{
	int* p = new int;
	*p = 100;
	cout << *p << endl;
}
int main()
{
	test();
	system("pause");
	return 0;
}

在这里插入图片描述

new[]

底层相当于循环去调用malloc(),这个函数一般用来开辟数组
实现原理:
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申

2. 在申请的空间上执行N次构造函数

void* operator new[](size_t size=sizeof(type)*n);

void test()
{
	int* p = new int[10];
	for (int i = 0; i < 10; i++)
	{
		p[i] = i * 10;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << p[i] << endl;
	}
}
int main()
{
	test();
	system("pause");
	return 0;
}

看底层
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

内存释放delete操作符

delete p;//释放p,释放单个变量

#define free(p) _free_dbg(p, _NORMAL_BLOCK)
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)//对传入指针判空
return;
_mlock(_HEAP_LOCK); /* block other threads */ //加锁,避免正在使用的释放
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );//可以看到底层实现是用free做的
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}

delete [] p ;//释放p所指向的数组空间
实现原理:
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

测试用例:

void test()
{
	int* p = new int;
	int* p1 = new int[10];
	delete p;
	delete[]p1;
}
int main()
{
	test();
	return 0;
}

c语言内存管理与C++内存管理的区别

1.C语言的三个在堆上的内存开辟函数的返回值需要进行强转(C++下),new的返回值不需要强转,因为在申请的时候编译器已经将其类型转好了
int *p=new int;//告诉编译器你要的是个int类型的数据。
2.C语言的开辟出来的空间的值除了calloc()函数之外,其与开辟出来空间的值皆为随机值。而C++的new可以初始化值。C++11中提出来的。

void test()
{
	int* p =(int *) malloc(sizeof(int) * 10);
	int* p1 = (int*)calloc(10, sizeof(int));
	//底下这个语句给初始值就从0号下标开始依次往下赋值,没有初始化的元素赋值为0;
	//若没有给初始值的话,比如 int *p2=new int[10];//此时里面的值全为随机值
	int* p2 = new int[10]{ 1 };
	free(p);
	free(p1);
	delete[]p2;

}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
可以看到 malloc出现的数组的值全为随机值,calloc为0,new中因为我只给了1个初始化变量,所以除了第一个元素外,其余9个全为0.
3.C语言的内存开辟函数开辟出来的类对象无法自动调用类的构造函数。而new出来的对象可以。同时,free()自动调用类的析构函数,而delete可以,

class date
{
public:
	date()
	{
		year = 1900;
		month = 0;
		day = 0;
		cout << "构造函数调用" << endl;
	}
	~date()
	{
		cout << "析构函数调用" << endl;
	}
private:
	int year;
	int month;
	int day;
};
void test01()
{
	date* m1 = (date*)malloc(sizeof(date) * 5);
	cout << "free调用" << endl;
	free(m1);
	
}
void test02()
{
date* m2 = new date[5];
cout << "delete调用" << endl;
delete[]m2;
}
int main()
{
	cout << "malloc开辟" << endl;
	test01();
	cout << "new 开辟" << endl;
	test02();
	return 0;
}

在这里插入图片描述
4.如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.在自定义方法分析:首先看C语言的内存开辟函数,它只开辟了与对象同等大小的空间,但是它不是对象。C++提供的new开辟内存,干了两件事,第一件事开辟内存,第二件事调用构造函数,完成对类对象变量的初始化。它开辟出来的是一个对象。对应的delete会调用析构函数,若你有析构函数,则调用你自身的析构,若没有,则调用系统自动生成的析构。

在使用的时候必须对应使用

malloc()对应free() new对应delete new[]对象 delete[]
必须做到匹配使用
如果不匹配使用,轻则的话内存泄露,重则的话程序会崩溃。
1.我们试一下malloc开辟让delete去释放。

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = (student*)malloc(sizeof(student));
	if (m1 == NULL)
	{
		cout << "内存开辟失败,退出" << endl;
		return;
	}
	delete m1;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述

2.用new开辟,free()释放

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = new student;
	free(m1);
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
3。new开辟,delete []释放

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = new student;
	delete[]m1;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
4.new[]开辟,free释放,崩溃
5.new[]开辟,delete释放,崩溃
注:一定要匹配使用。不然可能要不出现内存泄露,要不然程序崩溃。

同一个类,自定义析构函数比不自定义析构函数多4字节?

前提:new[]开辟,开辟类数组
是因为

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 100;
		
	}
	~student()
	{
		
		cout << "析构调用" << endl;
	}
private:
	int age;
	
};
void test()
{
	//student* m1 = new student;
	//delete m1;
	student* m2 = new student[10];
	delete[]m2;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
为了对比:
此时我们注释掉显式定义出的析构函数
此时我们再来看内存

在这里插入图片描述

重载new与delete操作符

class student
{
public:
	student()
	{
		age = 12;
	}
	~student()
	{

	}
	void* operator new(size_t size)
	{
		return malloc(size);
	}
	void operator delete(void* p)
	{
		if (p)
		{
			free(p);
		}
	}
private:

	int age;
};
int main()
{
	student* m1 = new student;
	delete m1;
}

在这里插入图片描述
在这里插入图片描述
不过这样重载感觉很蠢,我们一般会在固定需求时使用。

malloc()

malloc(size)为C语言开辟内存函数,它一般开辟size个大小,那么它真的就占这么大吗?当然不是,因为你要管理该空间,管理是不是要存储这部分的起始地址,和结束地址,所以在C/C++中有一个结构体,专门管理这部分空间。

#define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
   struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
   struct _CrtMemBlockHeader *pBlockHeaderPrev;
   char *szFileName;    // File name
   int nLine;                  // Line number
   size_t nDataSize;      // Size of user block
   int nBlockUse;         // Type of block
   long lRequest;          // Allocation number
// Buffer just before (lower than) the user's memory:
   unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

这是管理malloc的结构体的,共占32个字节。
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
可以看到开辟的空间是用双向链表组织起来的。

定位new

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test()
{
// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
}

new(pt) Test;没有去开辟空间,因为空间已经malloc出来了,只是让其去调用test的构造函数而已。

以上是关于C/C++内存管理的主要内容,如果未能解决你的问题,请参考以下文章

C/C++内存管理详解

C/C++内存管理详解

C++内存管理基础篇

系统C/C++内存管理之内存模型

C/C++动态内存创建与内存管理

[linux][c/c++]代码片段01