C/C++内存分布

🌎首先我们来看一看以下代码中变量在内存中的存储位置。

c/c++内存分配图:

1.栈又叫做堆栈,存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。

2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。

3.堆用于程序运行时动态内存分配,堆上可以向上增长的。

4.数据段 - 用于存储全局数据和静态数据。

5.代码段 - 可执行的代码/只读常量。

C语言中的动态内存管理

malloc/calloc/realloc/free

1

2

3

4

5

6

7

8

9

10

int main()

{

    int* p1 = (int*)malloc(sizeof(int));

    int* p2 = (int*)calloc(4, sizeof(int));

    int* p3 = (int*)realloc(p2, sizeof(int) * 10);

    free(p1);

    free(p2);

    free(p3);

    return 0;

}

malloc/calloc/realloc的区别?

malloc - 堆上动态开辟空间

realloc - 堆上动态开辟空间 + 初始化为0 (相当于malloc + memset)

calloc - 针对已经有的空间进行扩容 (原地扩容或异地扩容)

C++的内存管理

🚀C语言的内存管理方式在c++中可以继续使用,但是有些地方使用起来就比较麻烦了。因此c++提供了自己的内存管理方式:通过new和delete操作符进行动态内存管理。 new和delete是运算符,不是函数,因此执行效率高。

new和delete操作内置类型::

1

2

3

4

5

6

7

8

9

10

11

12

13

14

int main()

{

    // new/delete和malloc/free 针对内置类型没有任何差别,只是用法不同

    //动态申请一个int类型的空间

    int* p1 = new int;

    delete p1;

    //动态申请一个int类型的空间并初始化为10

    int* p2 = new int(10);

    delete p2;

    //动态申请10个int类型的空间

    int* p3 = new int[10];

    delete[] p3;

    return 0;

}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。

new和delete操作自定义类型::

注意: 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。

🌏对于以上总结一下:

1.c++中如果是申请内置类型对象或者数组,malloc和new没有什么区别。

2.如果是自定义类型,那么区别很大,new和delete是开空间 + 初始化,析构清理 + 释放空间,malloc和free仅仅是开空间 + 释放空间。

3.建议在c++中,无论是自定义类型还是内置类型的申请和释放,尽量使用new和delete。

operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new和 operator delete是系统提供的全局函数,new在底层调用operator new 全局函数来申请空间,delete在底层提供operator delete全局函数来释放空间。

如下是c++官方对于这两个函数的描述:

operator new和operator 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

43

/*

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;

}

/*

free的实现

*/

#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上诉两个全局函数的实现代码可以看出,operator new实际上是通过malloc来申请空间的,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足的应对,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。

operator 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

struct ListNode

{

    ListNode(int data = 0)

        :_next(nullptr)

        ,_prev(nullptr)

        ,_data(data)

    {}

    ListNode* _next;

    ListNode* _prev;

    int _data;

};

//operator new的用法跟malloc和free是一样的,都是在堆上申请空间

//只是申请空间失败后的处理方式不一样,malloc失败返回NULL,operator new失败以后抛异常

int main()

{

    //C语言

    ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));

    free(p1);

    //c++

    ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));

    operator delete(p2);

    int* p3 = (int*)malloc(100000000000000000);

    if (p3 == NULL)

    {

        cout << "malloc fail" << endl;

    }

    try

    {

        int* p4 = (int*)operator new(100000000000000000);

    }

    //开辟空间失败,捕获异常信息

    catch (exception& e)

    {

        cout << e.what() << endl;

    }

    return 0;

}

operator 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

class A

{

public:

    A(int a = 0)

    {

        cout << "A()" << this << endl;

    }

    ~A()

    {

        cout << "~A()" << this << endl;

    }

private:

    int _a;

};

int main()

{

    //c语言  ->  A* p = (A*)malloc(sizeof(A));

    //等价于直接用 A* p = new A;

    A* p = (A*)operator new(sizeof(A));

    new(p)A; // new(p)A(2);  定位new,placement-new,显示调用构造函数初始化这块空间对象

    //等价于 delete p

    p->~A();  //析构函数可以显示调用

    operator delete(p);

    return 0;

}

operator new与operator delete的类专属重载

内存池:内存池的主要作用是提高效率。通过一次性申请比较大的空间,来避免小空间内存的频繁申请和释放,每次需要为对象分配内存空间时,在已经申请好的大的空间内分配。空闲区被按照对象大小划分为若干块,每个块之间通过链表连接起来。

☑️以下代码演示了,针对链表的节点ListNode通过重载类专属 operator new / operator 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

43

44

45

46

47

struct ListNode

{

    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;

    }

    ListNode* _next;

    ListNode* _prev;

    int _data;

};

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 h;

    return 0;

}

new和delete的实现原理

内置类型:

若申请的是内置类型的空间,new和malloc,delete和free基本相似。new/delete 申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间。new在空间申请失败时会抛异常,malloc申请空间失败则返回NULL。

自定义类型:

new:

  1. 调用operator new函数申请空间。
  2. 在申请的空间调用构造函数,完成对象的构造。

delete:

  1. 在空间上调用析构函数,完成对象中资源清理的工作。
  2. 调用operator delete函数释放对象的空间。

new arr[N]:

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。
  2. 在申请的空间上执行N次构造函数。

更多推荐