【Cpp】第三章-内存管理

内存管理

C++内存管理

  在C语言中,我们想要动态分配内存空间需要使用到malloc,calloc,realloc函数,在C++中我们同样有动态进行内存管理的方式,并且与C语言中的内存管理有着一些区别。

new/delete

  在C++中我们使用new进行内存的申请,用delete进行内存的释放。他们的使用比mallocfree更加简单方便。

内置类型的内存分配与释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
int main()
{
int* a = new int;
//等同于int* a = (int*)malloc(sizeof(int));
int* b = new int[10];
//等同于int* b = (int*)malloc(sizeof(int) * 10);
int* c = new int(10);
//new还可以进行内置类型的初始化
cout << *c << endl;
delete a;
//等同于free(a);
delete[] b;//对于多个变量的空间释放要用delete[]
//等同于free(b);
delete c;
//等同于free(c);
}


[misaki@localhost 第三章-内存管理]$ ./New
10

  newmalloc一样会在堆上开辟空间同时需要我们手动进行内存的释放,但是new的写法更加简单易于理解同时我们还可以对单个申请的变量进行初始化。

自定义类型的内存分配与释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>                                 
#include <stdlib.h>
using namespace std;
class Stu
{
public:
Stu()
{
cout << "default building" << endl;
};
Stu(int num, string name):_num(num), _name(name)
{
cout << "custom building" << endl;
}
~Stu()
{
cout << "destroying" << endl;
}
private:
int _num;
string _name;
};
int main()
{
cout << "malloc:" << endl;
Stu* a = (Stu*)malloc(sizeof(Stu));
cout << "new:" << endl;
Stu* b = new Stu(1, "张三");
cout << "malloc:" << endl;
Stu* c = (Stu*)malloc(sizeof(Stu) * 5);
cout << "new:" << endl;
Stu* d = new Stu[5];
cout << "free:" << endl;
free(a);
cout << "delete:" << endl;
delete b;
cout << "free:" << endl;
free(c);
cout << "delete:" << endl;
delete[] d;
}


[misaki@localhost 第三章-内存管理]$ ./New
malloc:
new:
custom building
malloc:
new:
default building
default building
default building
default building
default building
free:
delete:
destroying
free:
delete:
destroying
destroying
destroying
destroying
destroying

  以上这段代码我分别使用mallocnew对自定义类型进行内存分配和释放,我们可以发现new不但可以在分配内存的时候手动调用指定的构造函数还会在分配多个对象的空间时自动调用默认构造函数,delete也会自动调用析构函数,而mallocfree却做不到这一点。因此可以理解mallocfree分配出来的只不过是一个和类一样大小的空间,并不能称作是一个对象,而newdelete分配出来的才能被成为对象

new和delete实现原理

  newdelete在C++中其实被定义为两个运算符,我们在使用这两个运算符的时候它会在底层调用全局函数operator newoperator delete

operator new/operator delete

  我们首先看下这两个函数在底层的实现源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

  从源码中能看出的是operator newoperator delete在底层也是利用mallocfree分配内存的,因此可以说newdelete不过是mallocfree的一层封装。因此在某些情况下,我们想要用独特的方式给一个类分配内存空间的时候我们就可以重新重载这两个运算符来达到我们的目的。基于这个原理如果有某些类需要特殊的内存分配方式我们可以对其进行运算符的重载。

实现原理

内置类型

  对于内置类型来说newmalloc,deletefree的功能一致,不同的是new[]delete[]才能分配多个连续的空间。

自定义类型

单个元素空间分配

  1、new:

    1)首先调用operator new为对象分配空间。

    2)调用对象的构造函数对对象进行初始化。

  2、delete:

    1)调用对象的析构函数进行对象中资源的空间清理。

    2)调用operator delete释放对象的空间。

多个元素空间分配

  1、new[]:

    1)调用operator new[],在operator new[]中调用operator new完成N个对象的空间的分配。

    2)调用构造函数N次完成N个对象的初始化。

  2、delete[]:

    1)调用析构函数N次完成N个对象资源的清理。

    2)调用operator delete[],在operator delete[]中调用operator delete完成N个对象的空间的释放。

定位new表达式

  当我们用malloc或者其他方式分配了一块和某个类一样大小的空间给,并用某个指针去指向这块空间。但是问题在于这块空间并未执行构造函数因此并不能称为对象。因此定位new表达式就是为了帮助我们对一块已经分配好的空间执行构造函数使之成为对象的一个方式。

用法

  new (place_address) type(initializer-list)。

  place_address为指向某一块空间的指针,type为自定义类型,initializer-list为参数列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>                                 
#include <stdlib.h>
using namespace std;
class Stu
{
public:
Stu()
{
cout << "default building" << endl;
};
Stu(int num, string name):_num(num), _name(name)
{
cout << "custom building" << endl;
}
~Stu()
{
cout << "destroying" << endl;
}
private:
int _num;
string _name;
};
int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
cout << "定位new表达式:" << endl;
new(p) Stu(1,"张三");
delete p;
}


[misaki@localhost 第三章-内存管理]$ ./New
定位new表达式:
custom building
destroying

  这样我们就用定位new表达式给已经分配好的空间调用了构造函数。

常见问题

new和malloc的异同

相同

  1、newdelete都在堆上进行空间的申请。

  2、都需要手动释放空间。

不同

  1、mallocfree是函数而newdelete是运算符。

  2、new可以在分配空间的时候进行初始化。

  3、malloc返回值是void*需要强转,new会直接返回与分配空间类型一样的类型指针。

  4、malloc需要手动计算分配空间大小在进行传入,而new只需要类型和元素个数,空间大小会自动计算。

  5、new在给自定义类型分配空间的时候会自动调用其构造函数,delete会自动调用其析构函数。

  6、malloc申请空间失败会返回NULLnew会抛异常。

  7、newdeletemallocfree的一层封装,因此效率会低一些。

写一个只能在堆上创建对象的类

思路

  1、将构造函数,赋值构造函数全部封装为私有,不允许外部直接调用构造。

  2、单独写一个静态函数提供在堆上创建对象的接口。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
class HeapOnly
{
public:
//开放构造接口
static HeapOnly* Create()
{
cout << "create int heap" << endl;
return new HeapOnly();
}
~HeapOnly()
{
cout << "destorying" << endl;
}
private:
//构造函数私有化
HeapOnly(){}
HeapOnly(const HeapOnly&){}
};
int main()
{
HeapOnly* heapOnly = HeapOnly::Create();
delete heapOnly;
}


[misaki@localhost 第三章-内存管理]$ ./HeapOnlyClass
create int heap
destorying

写一个只能在栈上创建对象的类

思路一

  1、将构造函数,赋值构造函数全部封装为私有,不允许外部直接调用构造。

  2、单独写一个静态函数提供在栈上创建对象的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;
class StackOnly
{
public:
static StackOnly Create()
{
//创建匿名对象
return StackOnly();
}
static StackOnly a;
~StackOnly()
{
cout << "destorying" << endl;
}
private:
StackOnly()
{
cout << "create in stack" << endl;
}
StackOnly(const StackOnly&){}
};
int main()
{
StackOnly::a = StackOnly::Create();
}


[misaki@localhost 第三章-内存管理]$ ./StackOnlyClass
create in stack
destorying

思路二

  1、直接将operator newoperator delete重载并定义为私有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>                     
using namespace std;
class StackOnly
{
public:
StackOnly()
{
cout << "create in stack" << endl;
}
~StackOnly()
{
cout << "destorying" << endl;
}
private:
void* operator new(size_t size){}
void operator delete(void* p){}
};


int main()
{
StackOnly p;
}

实现单例模式

  单例模式是一种设计模式,在实战中经常会用到。其意思是创建一个类这个类只能唯一的创建一个对象,如果之后还想用这个类创建新对象的时候都会返回最开始创建的呢个对象。

  要实现这一点有两种思路,分别成为懒汉模式饿汉模式

饿汉模式

  饿汉模式是在程序启动时就夹在所有需要资源的设计模式,用这种思想实现单例模式时需要在程序一开始就直接声明对象,需要适用对象就返回对象即可。但是坏处是程序启动时会消耗时间可能会造成程序启动缓慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
class Singleton1
{
public:
//外留接口
static Singleton1* GetInstence()
{
return &_singleton;
}
private:
//防拷贝,禁用构造,拷贝构造和赋值
Singleton1()
{

}
Singleton1(const Singleton1&);
Singleton1& operator=(const Singleton1&);
static Singleton1 _singleton;
};
Singleton1 Singleton1::_singleton;

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Singleton2
{
public:
static Singleton2* GetInstence()
{
//双重判断避免不必要的锁竞争
if(_pSingleton == nullptr)
{
//为了线程安全需要加锁
_mtx.lock();
if (_pSingleton == nullptr)
{
_pSingleton = new Singleton2;
}
_mtx.unlock();
}
return _pSingleton;
}
private:
Singleton2(){}
Singleton2(const Singleton2&);
Singleton2& operator=(const Singleton2&);
static std::mutex _mtx;
static Singleton2* _pSingleton;
};
std::mutex Singleton2::_mtx;
Singleton2* Singleton2::_pSingleton = nullptr;

  懒汉模式就是在第一次使用时才会创建对象,因此我们需要对变量进行判断考虑到线程安全我们需要对其加锁。懒汉模式的坏处是可能造成程序运行卡顿。

-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助