不落辰

知不可乎骤得,托遗响于悲风

0%

new_delete

new、malloc区别。free、delete区别

  • malloc free 称为C的库函数
  • new delete 称作运算符
    1
    2
    3
    4
    5
    int *p = malloc();
    free(p);
    ==================================
    int *p = new int(...);
    delete p;

malloc和new的区别?

  • new调用operator newoperator new调用malloc
  • malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10];所以malloc开辟内存返回的都是void*
  • malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化new int(20); new int[20]();
  • malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int* p = (int*)malloc(sizeof(int));
    if (p == nullptr) cout << "wrong" << endl;

    try {
    int* pp = new int(10);
    }
    catch (const bad_alloc& e) {

    }

    // int *pp = new int[20]; 在堆上开辟后不会初始化
    int *pp = new int[20](); // 在堆上开辟后会初始化
    delete []pp;

new

  • 先分配memory,再调用ctor(构造函数)
  • Complex* pc = new Complex(1,2);
  • 编译器转化为
    1
    2
    3
    4
    Complex *pc;
    void* mem = operator new(sizeof Complex); //分配内存 operator new 内部调用malloc(n)
    pc = static_cast<Complex*>(mem); //强转
    pc->Complex::Complex(1,2); //构造 Complex::(pc,1,2); pc即为this

    operator new

  • operator new仅仅负责开辟内存,在内存上写什么、任何与对象内容有关系的事情都与operator new无关
    1
    2
    3
    4
    5
    6
    7
    void *operator new(size_t size)
    {
    void *p = malloc(size); // 按Byte
    if(p==nullptr)
    throw bad_alloc();
    return p;
    }

有几种new

1
2
3
4
5
6
7
int *p1 = new int(20);
int *p2 = new (nothrow) int;
const int *p3 = new const int(20);
// 定位new 在指定内存上构造对象
int data = 0;
int *p4 = new (&data) int(5);
cout<<data<<endl; // 5

free和delete的区别?

  • delete调用operator delete,operator delete 调用free
  • delete (int*)p: 调用析构函数;再free(p)

new和delete能混用吗?C++为什么区分单个元素和数组的内存分配和释放呢?
new delete
new[] delete[]
对于普通的编译器内置类型 new/delete[] new[]/delete

自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,
会多开辟4个字节,记录对象的个数

delete

  • 先调用dtor,再释放内存memory
  • Complex *pc = new Complex(1,2);
    delete pc
  • 编译器转化为
    1
    2
    3
    Complex::~Complex(pc);  //dtor 调用析构函数是为了释放(基类、派生类)成员指针管理的内存,而不会释放本对象本身占据的内存。
    // 如果本对象是栈上的,那么离开作用域后会自动调用析构函数 ,然后释放内存(也就是说析构函数何释放本对象内存是两回事)。很可惜,new出来的对象在堆上,得手动调用operator delete释放。(所以要保证pc指向得位置是对的(虚基类里就不对))
    operator delete(pc); //释放memory 内部调用 free(pc)

operator delete

  • operator delete仅仅负责释放内存,在内存上析构什么对象、任何与对象内容有关系的事情都与operator new无关。之前就已经做完了。
    1
    2
    3
    4
    void operator delete(void *ptr)
    {
    free(ptr);
    }

new[] delete[] ; new delete

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
/*
* new:
* void *mem = operator new(size);
* p = static_case<>(mem);
* p->crot();;
*
* delete:
* p->dtor;
* operator delete
*/

void* operator new(unsigned long long size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new : " << p << endl;
return p;
}

void operator delete(void* ptr)
{
cout << "operator delete : " << ptr << endl;
free(ptr);
}

void* operator new[](unsigned long long size)
{
void* p = malloc(size);
if (p == nullptr) {
throw bad_alloc();
}
cout << "operator new[] " << p << endl;
return p;
}


void operator delete[](void *ptr)
{
cout << "operator delete[] " << ptr << endl;
free(ptr);
}
  • new[] delete[]正常使用
  • new[]
    • 除了开辟对象内存之外,还会在栈顶方向(p-8)开辟一块内存用于记录对象个数。
    • 用于释放数组时,得知需要调用几个对象的析构函数
    • delete时会从那块内存(p-8)开始释放
  • delete[]
    • 会从对象栈顶方向4/8bytes开始。也会把那块内存的数据当作要析构的对象个数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      Test* p2 = new Test[5]; 
      // 除了开辟对象内存之外,还会在栈顶方向(p-8)开辟一块内存用于记录对象个数。
      // delete时也会连带释放那块内存(p-8)
      cout << "p2:" << p2 << endl;
      delete[] p2; // Test[0]对象析构, 直接free(p2)

      operator new[] 0000021E55300DF0
      Test()
      Test()
      Test()
      Test()
      Test()
      p2:0000021E55300DF8
      ~Test()
      ~Test()
      ~Test()
      ~Test()
      ~Test()
      operator delete[] 0000021E55300DF0
  • new delete
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Test* p1 = new Test();
    cout << "p1 : " << p1 << endl;
    delete p1;

    operator new : 000001A1BF04D6B0
    Test()
    p1 : 000001A1BF04D6B0
    ~Test()
    operator delete : 000001A1BF04D6B0
  • new delete[]混用后果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class Test
{
public:
Test(int data = 10) { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
private:
int ma;
};

int main()
{
Test* p1 = new Test();
delete[]p1;
}

operator new : 000001D8BC681C50
Test()
p1 : 000001D8BC681C50
~Test()
~Test()
~Test()
~Test() ...死循环
  • new[] delete
    • 只会调用一个对象的析构函数,并且用于记录对象个数的内存没有被释放

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Test* p2 = new Test[5];
      cout << "p2:" << p2 << endl;
      delete p2; // Test[0]对象析构, 直接free(p2)

      operator new[] 00000299D1C59BA0
      Test()
      Test()
      Test()
      Test()
      Test()
      p2:00000299D1C59BA8
      ~Test()
      operator delete : 00000299D1C59BA8

对象池

  • 简单来说:将对象所占的内存提前开辟出来,放到一个池子里面(链表维护),当需要构造对象的时候,就从池子里面取出内存来在那上面构造。

链式队列 + 内置对象池

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include<iostream>
using namespace std;

// 对象池 链式队列
template<typename T>
class Queue
{
public:
Queue()
{
_front = _rear = new QueueItem();
}
~Queue()
{
QueueItem *cur = _front , *ne=nullptr;
while(cur)
{
ne = cur->_next;
delete cur; // delete 时 会调用QueueItem类内重载的operator delete。while会将这些节点全部加入内存池。不过没释放。
cur = ne;
}
}

void push(const T& val)
{
_rear->_next = new QueueItem(val); // 产生节点
_rear = _rear->_next;
}

void pop()
{
if(empty()) return ;
QueueItem *first = _front->_next;
_front->_next = first->_next;
delete first;
if(_front->_next==nullptr)
{
_rear = _front;
}
return ;
}

T front() {return _front->_next->_data;}
bool empty() {return _front==_rear;}
private:
/*
* new : operator new ; 强转 ; 构造函数 ; 返回
*/
// 给QueueItem提供内存管理 operator new ; operator delete
struct QueueItem // 内置节点类型
{
QueueItem(T data = T() , QueueItem *next=nullptr)
:_data(data),_next(next){}

// 编译器会自动处理为static
// 对象池为空时,再造一个对象池。(这个对象池的节点和前一个对象池的节点不是割裂的。之后会通过pop()(_next)连上的
// 对象池不为空时,从对象池里取对象。
void* operator new(size_t size) // size没有用,只是为了构成重载
{
if(_itemPool==nullptr)
{
cout<<"!"<<endl;
// 分配足够大内存 用作空闲池
_itemPool = (QueueItem*) malloc(sizeof(QueueItem)*POOL_ITEM_SIZE);
QueueItem *p = _itemPool;
// 虽然还没构造这个QueueItem对象,不过QueueItem类型指针指向那里,就认为那里的内存存储的数据是QueueItem对象。p也就认为相应位置存在着一个相应成员
// operator new之后才调用ctor构造函数。构造函数的作用也就是给相应位置的内存存入相应数据。并没有开辟内存的作用
// p指向的内存已经被申请来,不用担心会被程序的其他指令要求占用
for(;p<_itemPool + POOL_ITEM_SIZE - 1;++p)
{
p->_next = p+1;
}
p->_next = nullptr; // 最后一个空闲节点的下一位置 为空 代表空闲池耗尽。走到nullptr就意味着我们要造新池子。
}
// 现在要从这堆空闲内存取出一个 取出链表头部的
QueueItem *p = _itemPool;
_itemPool = _itemPool->_next;
return p; // 接下来要通过p在p指向的内存上调用构造函数 (此时p的_next会被改变,不再指向下一个空闲节点,无所谓。本来就该这样。此时p应该在链式队列的链表上。而非对象池的链表上)
}

// 管理已经无用的内存块 将其放入空闲内存链表中
void operator delete(void *ptr)
{
// cout<<ptr<<endl;
// 头插法 加入对象池。而非释放这块内存。
QueueItem *p = static_cast<QueueItem*>(ptr);
p->_next = _itemPool->_next;
_itemPool->_next = p;
}

T _data;
QueueItem *_next;
static QueueItem *_itemPool; // 指向内存池的首个节点(即第一块空闲内存)
const static int POOL_ITEM_SIZE = 10000; // 新标准 static常量可以类内初始化。
};
QueueItem *_front; // 头节点(无意义)
QueueItem *_rear;
};

template<typename T> // 模板名 + 参数名 才是类型 所以是Queue<T>::
typename Queue<T>:: QueueItem*
Queue<T>::QueueItem::_itemPool = nullptr;

int main()
{
Queue<int> q;
for(int i=0;i<10000;++i)
{
q.push(rand()%20);
q.pop();
}
cout<<q.empty()<<endl;
}
// !
// 1
  • 新标准:静态常量可以在类里面初始化,不用在外面初始化。