C/C++内存管理
一.C/C++内存分布
在程序运行时需要存储一些数据,而不同的数据对应不同的需求。计算机内从用途和存储的角度对不同的数据进行了区域的划分。
栈区是通过调用函数,来建立栈帧。建立栈帧的本质是用来存储局部数据,做到即用即销毁。
1.1试题
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
答案:C C C A A A A A D A B
答案: 40, 5, 4, 4/8, 4, 4/8
二.动态内存管理方式
2.1 C动态内存管理方式
void test1()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
p2 = NULL;
free(p1);
free(p3);
}
2.2 C++动态内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
void test2()
{
// 动态申请一个int类型的空间
int* p4 = new int;
delete p4;
// 动态申请一个int类型的空间并初始化为10
int* p5 = new int(10);
delete p5;
//动态申请10个int类型的空间
int* p6 = new int[10];
delete[] p6;
//动态申请10个int类型的空间并初始化
int* p7 = new int[10] { 1, 2, 3, 4};
delete[] p7;
}
注意:
- 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。
- malloc使用free来释放,new使用delete来释放,一定要匹配使用。
三.C/C++动态内存管理方式的不同
3.1动态申请内置类型
void test1()
{
//C语言中动态内存管理方式
//需要算多少字节和进行类型的强转
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
p2 = NULL;
free(p1);
free(p3);
//C++中动态内存管理方式
int* p4 = new int;
delete p4;
int* p5 = new int(10);
delete p5;
int* p6 = new int[10];
delete[] p6;
int* p7 = new int[10] { 1, 2, 3, 4};
delete[] p7;
}
相同点:如果不主动初始化,两种方式都不会主动初始化。
不同点:
1.malloc在申请空间时不能同时进行初始化,new则可以.
2.new使用起来更加简洁,不用进行计算大小和强转。
3.2动态申请自定义类型
struct ListNode
{
int _val;
struct ListNode* _next;
ListNode(int x)
:_val(x)
, _next(nullptr)
{
cout << "ListNode(int x)" << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
//C
struct ListNode* BuyListNode(int x)
{
struct ListNode* newnode =
(struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc failed");
return NULL;
}
newnode->_val = x;
newnode->_next = NULL;
return newnode;
}
void test2()
{
//C语言:调用函数,malloc,初始化
//其中malloc只是单纯的开空间
struct ListNode* n1 = BuyListNode(0);
struct ListNode* n2 = BuyListNode(1);
free(n1);
free(n2);
//C++
//开空间的同时,调用构造函数进行初始化
//delete时会调用析构函数
ListNode* n4 = new ListNode(3);
ListNode* n5 = new ListNode(4);
delete n4;
delete n5;
}
不同点:
- new会调用构造函数进行初始化,delete会调用析构函数进行清理。而malloc只是开空间。
2.new使用起来比malloc更加便捷。
3.3无默认构造函数的自定义类型
- 有默认构造函数
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//有默认构造函数
//直接调用默认构造函数初始化
A* p6 = new A[4];
delete[] p6;
return 0;
}
- 没有默认构造函数
class A
{
public:
A(int a)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//没有默认构造函数
//初始化方法一:隐式类型转换
//只能用于单参数的情况
A* p6 = new A[4]{0, 1, 2 ,3};
delete[] p6;
//方法二:匿名对象
//可以初始化多参数的情况
//构造 + 拷贝构造 -> 优化为构造
A* p7 = new A[4]{ A{1}, A{2}, A{3}, A{4} };
delete[] p7;
return 0;
}
四. new和delete的实现原理
malloc:开空间。 失败之后返回NULL。
new:开空间 + 构造函数。失败之后,抛异常。
new在开空间的时候不可以直接复用malloc。因为malloc失败之后返回NULL,而C++等面向对象的语言失败之后是抛异常,然后捕获异常。。
因此就出现了operator new对malloc进行封装,在调用malloc开空间,如果失败则抛出异常。同理delete也是如此,operator delete 最终也是通过free来释放空间的。
4.1 operator new与operator delete函数
operator new 和operator delete是系统提供的全局函数,new在底层调operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
new T[N]则是调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,在申请的空间上执行N次构造函数。delete[]在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理调operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
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);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
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);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
4.2使用场景
4.2.1申请一个堆上的栈对象
此时不能使用malloc和free,因为malloc只是开空间并未初始化,free也不能完成资源的清理工作。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack* p1 = new Stack;
delete p1;
return 0;
}
五.定位new表达式(replacement-new)
定位new表达式是在已分配的原始内存空间中显式调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
其中:place_address必须是一个指针,initializer-list是类型的初始化列表
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间
//还不能算是一个对象,因为构造函数没有执行
//显式调用构造函数
A* p1 = (A*)malloc(sizeof(A));
new(p1)A(1); // 注意:如果A类的构造函数有参数时,此处需要传参
//p1此时是指针属于内置类型,不会主动去调用析构函数
p1->~A();
free(p1);
return 0;
}
5.1使用场景
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
六.*malloc/free和new/delete的区别
共同点:都是从堆上申请空间,并且需要用户手动释放。
不同点:
特性和用法方面:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc使用起来比较复杂,需要计算大小,强转。new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数。
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空。new不需要,但是new需要捕获异常。
底层方面:
针对自定义类型,new和delete的出现弥补了malloc和free的不足。malloc/free只会开辟空间。而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理