目录
三,operator new/operator delete函数(重点)
一,C/C++内存分布
- 栈(又称堆栈),非静态局部变量、函数参数、返回值等,向下增长;
- 内存映射段,是高效的I/O映射方式,用于装载一个共享的动态内存库;用户可使用系统接口共享共享内存,做进程通信;
- 堆,用于程序运行时动态内存分配,向上增长,但不一定有可能使用释放后的低地址;
- 数据段,存储全局数据和静态数据;
- 代码段,存储可执行代码和只读常量;
二,内存管理方式
C语言中动态内存管理方式:malloc、calloc、realloc、free;
C++内存管理方式
new是C++关键字,也是一个运算符,用于对象实例化时,在堆上动态开辟内存,并返回该内存地址;
- 兼容C语言内存管理方式;
- 通过new/delete操作符,进行动态内存管理;
- 动态申请内置类型,与malloc无区别;
- 动态申请自定义类型,不仅仅会开空间/释放空间,还会调用构造函数和析构函数;
语法:
//单个类型的申请及释放 type* ptr = new type; delete ptr;
//数组类型的申请及释放 type* ptr = new type[numbers]; delete[] ptr;
//类对象类型的申请及释放,会调用构造函数和析构函数 //可带构造函数参数列表 type* ptr = new MyClass(arg1,arg2,...) delete ptr;
//new/delete内置类型
int main()
{
//动态申请一个int类型的空间
int* ptr1 = new int;
//动态申请一个int类型的空间,并赋值10
int* ptr2 = new int(10);
//动态申请10个int类型的空间
int* ptr3 = new int[10];
delete ptr1;
delete ptr2;
delete[] ptr3;
return 0;
}
//new/delete自定义类型
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//申请一个A类型的空间
A* ptr1 = (A*)malloc(sizeof(A));
free(ptr1);
//申请10个A类型的空间
A* ptr2 = (A*)malloc(sizeof(A) * 10);
free(ptr2);
//申请一个A类型的空间
A* ptr3 = new A; //或带初始化值new A(10)
delete ptr3;
//申请10个A类型的空间
A* ptr4 = new A[10];
delete[] ptr4;
return 0;
}
三,operator new/operator delete函数(重点)
operator new/operator delete函数
- new/delete是动态申请和释放的操作符;
- operator new/operator delete是系统提供的全局函数;
- new在底层:调用operator new全局函数来申请空间 + 构造函数;
- delete在底层:析构函数 + 调用operator delete全局函数来释放空间;
- operator new实际是通过malloc来申请的;
- operator delete最终是通过free来释放空间;
//new与malloc区别,new失败会抛异常;
int main()
{
char* p1 = (char*)malloc((size_t)2 * 1024 * 1024 * 1024);
//检查p1
if (p1 == NULL)
perror("p1");
try
{
char* p2 = new char[0x7fffffff]; // 抛异常,捕获异常位置
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
new动态申请时:先调用operator new,在调用构造函数;
delete释放时:先调用析构函数,在调用operator delete;
operator new/operator delete类专属重载(了解)
struct ListNode
{
ListNode(int data = 0)
:_next(nullptr)
,_data(data)
{}
ListNode* _next;
int _data;
//类专属重载
//new ListNode申请空间,就会调用此处类专属重载
void* operator new(size_t n)
{
++_count;
return ::operator new(n); //::是避免自己调用自己,::前为空则为调用全局
}
void operator delete(void* p)
{
--_count;
return ::operator delete(p);
}
static int _count;
};
int ListNode::_count = 0;
struct ListNode* removeElements(ListNode* head, int val)
{
ListNode* prev = head;
ListNode* cur = head;
while (cur)
{
if (cur->_data == val)
{
prev->_next = cur->_next;
delete cur;
cur = prev->_next;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return head;
}
int main()
{
//创建时先申请空间,在构造函数
ListNode* node1 = new ListNode(1);
ListNode* node2 = new ListNode(2);
ListNode* node3 = new ListNode(3);
ListNode* node4 = new ListNode(2);
ListNode* node5 = new ListNode(5);
node1->_next = node2;
node2->_next = node3;
node3->_next = node4;
node4->_next = node5;
removeElements(node1, 2);
cout << ListNode::_count << endl;
return 0;
}
class Foo
{
public:
Foo() { cout << "Foo()" << endl; }
~Foo() { cout << "~Foo()" << endl; }
static void* operator new(size_t size) throw(std::bad_alloc);
static void operator delete(void* pMem);
private:
int _data;
};
//重载operator new
void* Foo::operator new(size_t size) throw(std::bad_alloc)
{
cout << "allocating " << size << " bytes" << endl;
void* pMem = malloc(size);
if (!pMem)throw bad_alloc();
memset(pMem, 0, size);
return pMem;
}
//重载operator delete
void Foo::operator delete(void* pMem)
{
cout << "deallocating memory!" << endl;
free(pMem);
}
int main()
{
Foo* p = new Foo;
delete p;
return 0;
}
四,new/delete实现原理
内置类型
- new与malloc,delete与free基本类似;
- new/delete申请和释放的是单个元素空间;
- new[]/delete[]申请和释放的是连续空间,且new申请失败会抛异常;
自定义类型
new原理
- 调用operator new函数申请空间;
- 在申请空间上执行构造函数,完成对象构造;
delete原理
- 在空间上执行析构函数,完成对象中资源的清理工作;
- 调用operator delete函数释放对象的空间;
new Type[N]原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请;
- 在申请的空间上执行N次构造函数;
delete[]原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间;
class Stack
{
public:
Stack(int capacity = 4)
:_a(new int[capacity])
,_size(0)
,_capacity(capacity)
{
cout << "Stack():" << this << endl;
}
~Stack()
{
delete _a;
_size = _capacity = 0;
cout << "~Stack():" << this << endl;
}
private:
int* _a;
int _size;
int _capacity;
};
//先在栈上main内,开辟Stack大小的空间
//在调用构造函数,在堆上开辟16字节大小空间给_a,在初始化_size/_capacity
//最后main结束时,调用析构函数,销毁堆上开辟16字节空间,_size/_capacity赋零
int main()
{
Stack st;
return 0;
}
//先在堆上开辟Stack大小空间给指针p
//在开辟Stack空间时,调用构造函数,再在堆上开辟16字节大小空间给_a,在初始化_size/_capacity
//在delete时,调用析构函数,销毁堆上开辟16字节空间,_size/_capacity赋零,在释放堆上的Stack空间
int main()
{
Stack* p = new Stack;
delete p;
return 0;
}
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
A(const A& a)
{
_a = a._a;
cout << "A(const A& a)" << this << endl;
}
A& operator = (const A& a)
{
_a = a._a;
cout << "A& operator = (const A& a)" << this << endl;
return *this;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A a[5]; //调用5次构造
A* p1 = new A[5]; //调用5次构造
for (int i = 0; i < 5; i++)
{
*(p1 + i) = a[i]; //调用5次赋值重载
}
cout << endl;
return 0;
}
int main()
{
A a[5]; //调用5次构造
A* p1 = (A*)malloc(sizeof(A) * 5);
cout << endl;
for (int i = 0; i < 5; i++)
{
//也称replacement-new
new(p1 + i) A(a[i]); //调用5次拷贝构造
}
cout << endl;
return 0;
}
五,定位new表达式(placement-new)(了解)
- 定位new表达式,是在已分配的原始内存空间中调用构造函数初始化一个对象;
- 实际中一般配合内存池使用,因为内存池分配出的内存没有初始化,所以如是自定义类型对象,需使用new的定义表达式进行显示调构造函数进行初始化;
格式:
- new(place_address) type 或 new(place_address) type(initializer-list)
- place_address必须为一个指针,initializer-list是类型的初始化列表;
class A
{
public:
A(int a = 0, int val = 0)
:_a(a)
,_val(val)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
int _val;
};
int main()
{
A* p = (A*)malloc(sizeof(A));
new(p) A(1,2);
return 0;
}
六,附
malloc/free和new/delete共同点
- 都是堆上申请空间;
- 需手动释放;
malloc/free和new/delete不同点
- malloc/free是函数,new/delete是操作符;
- malloc申请空间时需传递空间大小,new只需空间类型即可;
- malloc返回void*需强转,new返回空间类型;
- malloc申请失败返回NULL使用需判空,new会抛异常;
- 申请自定义类型时,malloc/free不会调用构造函数/析构函数,new申请空间后会调用构造函数初始化,delete在释放空间前会调用析构函数;
内存泄露
- 是指因为疏忽或错误导致程序未能释放已经不使用的内存的情况;
- 并不是内存在物理上的消失,而是失去对该段内存的控制,造成内存的浪费;
- 长期运行的程序出现内存泄露,如操作系统或后台服务,可能会导致响应越来越慢最终卡死;
内存泄露分类(了解)
- 堆内存泄露(Heap Leak),是指程序从堆中分配的内存,未被释放;
- 系统资源泄露,是指程序使用系统分配的资源,如套接字、文件描述、管道等没有使用对应的函数释放掉,导致系统资源浪费;
避免内存泄露
- 智能指针;
- 内存泄露检查工具;
注:进程正常结束,都会释放;僵死进程、长期运行的服务器程序、物联网设备等不会释放;