要正直地生活,别想入非非!要诚实地工作,才能前程远大。
我们来看一看以下代码的变量分别存在哪?
#include
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);
}
局部变量都开在函数栈帧上,这个题简单,秒选。需要注意的是char2
是一个数组(做值的话代表的是首元素的地址),数组的空间在Test的函数栈帧上已经开辟好了,然后再把常量字符串"abcd"
拷贝过来,所以*char2
指向的内容也在栈上,这是语法性质决定的。而pchar3是一个指针,指向代码段的常量字符串"abcd"
。
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
我们先来看一下new
的几种用法:
1. new 1个int对象
int *p1 = new int;
2. new 多个int对象
int *p2 = new int[10];
3. new 1个int对象,初始化成10
int *p3 = new int(10);
4. new 多个int对象并初始化,C++11的特性
int *p4 = new int[10]{10,9,8,7};
new
出来的对象用delete
来释放,释放单个元素的空间,使用delete
操作符,释放连续的空间,使用delete[]
,如果不匹配,可能会报错。
delete p1;
delete[] p2;
delete p3;
delete[] p4;
注意:对于内置类型,用malloc
和new
,除了用法不同,没有什么区别。他们的区别在于自定义类型。
C语言链表开新节点:
#include
#include
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _val;
ListNode(int val = 0)
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{}
};
struct ListNode* BuyListNode(int x)
{
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
assert(node);
node->_next = NULL;
node->_prev = NULL;
node->_val = x;
return node;
}
C++直接new
就可以了:
int main()
{
//ListNode* n1 = BuyListNode(1);
ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
assert(n1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
ListNode* n4 = new ListNode(4);
return 0;
}
malloc
只开空间不初始化,所以C语言要写一个BuyListNode来开空间并初始化。new
会直接去调用链表节点的构造函数。
而且C++不需要检查,malloc
失败了是返回空指针,new
失败了是抛异常。
再比如我们写了一个栈:
class Stack
{
public:
Stack(int capacity = 0)
{
cout << "Stack(int capacity)" << endl;
_a = new int[capacity];
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_capacity = 0;
_top = 0;
}
void Push(int x)
{}
private:
int* _a;
int _top;
int _capacity;
};
我们定义一个栈,用new
还是malloc
,delete
还是free
有没有什么区别呢?
ok,我们把两种方式都写一遍,借助调试帮助大家理解。
int main()
{
Stack* ps1 = (Stack*)malloc(sizeof(Stack));
assert(ps1);
Stack* ps2 = new Stack;
free(ps1);
delete ps2;
return 0;
}
ps1、ps2
都是一个指针,它们指向一段动态开辟的空间。
new
会开空间并初始化(通过调构造函数),new
其实开了两段空间,第一个Stack
是new
开的,Stack
里的数组是new
去调用构造函数开的。
ps1
是malloc
的,不会初始化(想初始化也很难,因为栈的成员变量是私有的,而构造函数不能显示调用)。
那delete
又会去干什么事情呢?
free
是直接把ps1指向的空间干掉。
delete
为了防止内存泄漏,会先调用栈的析构函数清理资源,再释放外层的栈空间。
其实C语言也会存在这样的问题,像我们学习数据结构阶段刷过的一道题:两个栈实现队列就是这样。
而用C++写就不用这么麻烦:
new
会把MyQueue
的空间开好,MyQueue
的默认构造函数会去调用Stack
的构造函数。
而delete
会先调用MyQueue
的默认析构函数,MyQueue
的默认析构函数会调用Stack
的析构函数清理栈的资源,再释放MyQueue
的空间。
operator new
和operator delete
不是重载,而是C++的两个库函数。
new
在底层调用operator new
函数来申请空间,delete
在底层通过operator delete
函数来释放空间。
operator new
、operator delete
和malloc
、free
功能一样,只是失败了之后会抛异常。
所以operator new
和operator delete
是没有直接价值的,但是有间接价值:new
和delete
的底层原理。
下面代码演示了,针对链表的节点ListNode通过重载类专属 operator new/ operator delete,实现链表节点使用内存池申请和释放内存,提高效率。
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List l;
return 0;
}
很形象的一个例子,重复地向堆区申请空间,就相当于每月向妈妈要生活费。而内存池就相当于我们自己的钱包,妈妈每个月算好钱打到我们的钱包,我们自己拿钱(相当于向内存池申请空间)就要快得多。
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
- new的原理
- delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
- new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申
请- 在申请的空间上执行N次构造函数
- delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type
或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
class Stack
{
public:
Stack(int capacity = 0)
{
cout << "Stack(int capacity)" << endl;
_a = new int[capacity];
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_capacity = 0;
_top = 0;
}
void Push(int x)
{}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack* obj = (Stack*)operator new(sizeof(Stack));
// 针对一个空间,显示调用构造函数初始化
new(obj)Stack(4);
return 0;
}
其等价于Stack *obj = new Stack(4);
所以我们平时也不会这样写。
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。(实际上是指针丢了,找不到动态开辟的空间)。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
C/C++程序中一般我们关心两种方面的内存泄漏: