C++ study notes summary exercise: dynamic memory

dynamic memory

Existing problems: The allocation of stack space and heap space is also runtime memory allocation, that is, dynamic memory allocation. The literal constant area, global variable and static variable area are memory allocated at compile time, that is, static memory allocation.

Dynamic memory allocation of stack space is managed by the operating system. The dynamic memory allocation of the heap space is managed by the user itself. The two can be used interchangeably.

For example: using stack space cannot return local objects, only the copy of local objects can be returned (overriding the copy constructor), which is troublesome, you can return the heap space to apply for an object, and return a pointer to the object.

For example: using the heap space, the release of the object needs to be managed by itself. You can create a local object in the stack space, and encapsulate the method of applying and releasing the heap space inside it. When the local objects in the stack space are destroyed, the destructor is called to clear the objects in the heap space. This is also the implementation of allocator and smart pointer. (The allocator generally uses the destructor of the stack object to destroy the space of the heap object. The smart pointer generally destroys the heap object through the method of reference counting.)

There are two methods for dynamic allocation of assembly language programs:
Method 1: Obtain memory blocks from the operating system through system calls. (The linux/windows operating system provides the alloc method and the heap method. In the current process, the latter is used to apply for a new private heap, and the former is used to apply for memory on the existing heap space.) Method 2: Implement your
own The heap manager to service requests for smaller objects.

0 C++ directly manages memory

concept

  • C++ uses new to allocate memory, and delete to release the memory allocated by new.
  • The lifecycle of a dynamic object is from creation to release.

Reasons to use dynamic memory

Since there are so many risks, why use dynamic memory

  1. The program does not know how many objects it needs to use. A dynamic loop creates multiple objects. (Containers, including strings, use dynamic memory to manage their storage.)
  2. The program does not know the exact type of the object it needs. Objects are created at runtime.
  3. Programs need to share data among multiple objects. Multiple objects share the same state.

Apply for memory

  • new cannot name the object it allocates, but instead returns a pointer to that object.
int * p = new int;
  • Dynamically allocated objects perform default initialization. Objects of built-in types and composite types are undefined. Objects of class type are initialized using the default constructor.

  • Can dynamically allocate const objects

const int * pci = new const int(1024);
  • The new expression fails when memory is exhausted. A bad_alloc exception is thrown.

free memory

  • Use delete to free memory. Return dynamic memory to the system.
  • The delete pointer must point to dynamically allocated memory, or a null pointer.
  • The behavior of freeing a block of memory not allocated by new, or freeing the same pointer multiple times is undefined. An error is generated.
  • After deleting the object, the address pointed to by the pointer is released, and the pointer is invalid. But the pointer still holds the original address. Program dangling pointers .

All variable-length containers have their own allcator to implement dynamic memory management, and do not need to manually apply for and release memory.

How new and delete work

  • When we allocate memory using the new expression, there are actually three steps performed.
    1. is to call a standard library function named operator new (or operator new[]). This function allocates a raw block of unconstructed memory of sufficient size
    2. The compiler runs the constructor to construct the object
    3. returns a pointer to the object
  • Similarly, delete performs two steps:
    1. Execute object destructors in reverse order
    2. Call operator delete or (operator delete[]) standard library function to release memory space

1 Dynamic memory and smart pointers

So smart pointers are used for dynamic memory management. Ordinary local variables and their pointers, no need for smart pointers. Smart pointers assist new delete to manage dynamic memory. The smart pointer is to solve the problem.

two questions

There are two problems with direct memory management by pointers:

  1. The memory is not freed, resulting in a memory leak.
  2. Access to memory that has been freed, causing a pointer that references illegal memory.

smart pointer

  • share_ptr allows multiple pointers to the same object

  • unique_ptr exclusively points to the object

  • weak_ptr companion class. Weak references. Points to the object managed by shared_ptr.

head File

#include<memory>

Basic operations of smart pointers

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-tj9VdNAU-1691409480326)(image/2021-03-06-17-05-19.png)]

smart pointer trap

The smart pointer is not a pointer in essence, it is the management of the pointer of the dynamically allocated memory, and the ordinary pointer pointing to the dynamic memory can be obtained through the get function.

  • cannot initialize multiple smart pointers with the same built-in pointer
  • Cannot delete the normal pointer returned by get()
  • You cannot initialize or reset another smart pointer with a pointer returned by get().
  • If the resource managed by the smart pointer is not memory allocated by new , pass it a deleter. (The deleter corresponding to the dynamic memory allocated by new is delete)
  • When the objects pointed to by two smart pointers use a shared_ptr member variable to point to each other, a circular reference will be caused, which will invalidate the reference count and cause a memory leak. For example they go out of scope at the same time, but there is no way to destroy either of them because they still keep a reference count to each other.

Smart pointer overview

  • Smart pointers are mainly used to manage memory allocated on the heap, which encapsulates ordinary pointers into a stack object. When the life cycle of the stack object ends, the allocated memory will be released in the destructor to prevent memory leaks.
  • The most commonly used smart pointer type in C++11 is shared_ptr, which uses a reference counting method to record how many smart pointers the current memory resource is referenced by. This reference counted memory is allocated on the heap . The reference count increases by 1 when a new one is added, and the reference count decreases by one when it expires. Only when the reference count is 0, the smart pointer will automatically release the referenced memory resource. When initializing shared_ptr, an ordinary pointer cannot be directly assigned to a smart pointer, because one is a pointer and the other is a class. Ordinary pointers can be passed in through the make_shared function or through the constructor. And ordinary pointers can be obtained through the get function.

2 Dynamic memory management shared_ptr

Operation of shared_ptr

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-9FYeKMGe-1691409480327)(image/2021-03-06-17-06-13.png)]

make_shared apply for memory

  • The safest way to use dynamic memory is to call the make_shared standard library function. Allocate an object in dynamic memory and initialize it. returns shared_ptr.
    shared_ptr<int> pn = make_shared<int>(42);
    auto pn = make_shared<int>(42);

shared_ptr copy and reference counting

  • shared_ptr has an associated counter. Reference counting. Every time a shared_ptr is copied, the counter is incremented. For example passing it as an argument to a function, or as the return value of a function.
  • Once the counter of a shared_ptr becomes 0, it will automatically release the memory it manages. Using a destructor, destroys itself.
  • So when it is a local variable and exits the local scope, all pointer variables are automatically destroyed, and the reference count of the corresponding dynamically allocated memory object returns to zero, and the dynamic memory will be automatically destroyed at this time.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-JaNHRU08-1691409480327)(image/2021-03-06-17-26-40.png)]

shared_ptr and new apply for memory

  • shared_ptr can use make_shared to create objects. You can also use the pointer returned by new to initialize a smart pointer. There is no need to delete to release at this time.
shared_ptr<int> p2(new int(42));
shared_ptr<int> p3 = new int{42};

Other methods of shared_ptr

  • Assignment, permission transfer and emptying of shared_ptr.
    [External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-GotonC7f-1691409480327)(image/2021-03-06-17-43-51.png)]

  • You cannot use get to initialize another smart pointer, nor can you use get to assign a value to a smart pointer.

  • We intelligently use reset to point smart pointers to other objects, and we cannot directly assign a new object to an initialized smart pointer. The reset function updates the object's reference count to point to new dynamic memory.

shared_ptr<int> = new int;
p = new int(23);//错误,不能直接赋值。
p.reset(new int(1024));//p指向一个新的对象。

Smart pointers and exceptions

  • When an exception is triggered, the destruction of the smart pointer will also be triggered normally when the local variable is destroyed.

3 Dynamic memory management unique_ptr

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-yD0HMpKc-1691409480327)(image/2021-03-06-17-58-42.png)]

  • unique_ptr. There can only be one unique_ptr for a given object at a time. When the unique_ptr is destroyed, the object it points to is also destroyed.
  • unique_ptr needs to be bound to a pointer returned by new. Directly set the pointer to null, and the object pointed to by the pointer will be released. You can use delete to release unique_ptr
  • unique_ptr does not support ordinary copy and assignment operations. But you can copy or assign a unique_ptr that will be destroyed. For example, return unique_ptr. Achieve transfer of control
unique_ptr<int> clone(int p){
    return unique_ptr<int>(new int(p));
}//返回了一个动态内存的unique_ptr
  • You can release to give up control and hand over control to a new smart pointer. Realize the control transfer of dynamic memory.
unique_ptr<int> m=new int;
unique_ptr<int> n = m.release();//m放弃所有权转移给n

4 dynamic memory management weak_ptr

  • A smart pointer that does not control the lifetime of the object it points to. It points to an object managed by shared_ptr.
  • Binding a weak_ptr to a shared_ptr does not change the reference count of the shared_ptr. When the shared_ptr is destroyed, the object is released.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-mcpy6fHW-1691409480328)(image/2021-03-06-18-09-20.png)]

  • Initialize weak_ptr with shared_ptr
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int>wp(p);
  • Since the object pointed to by weak_ptr may not exist, use lock to check whether the object pointed to by weak_ptr exists.
if(shared_ptr<int>np = wp.lock()){

}

Summarize

Smart pointer operations include: initialization, dereference, assignment, reset, get. The assignment operation is a special case, and other operations are consistent:

  • shared_ptr can be assigned between two smart pointers. reset can point the smart pointer to a new dynamic memory object. You cannot directly assign a new dynamic memory object to a smart pointer.
  • Unique_ptr cannot be assigned between two smart pointers, but it can be assigned between smart pointers that are about to be destroyed. You cannot directly assign a new dynamic memory object to a smart pointer. reset can point the smart pointer to a new dynamic memory object.
  • weak_ptr can assign shared_ptr and weak_ptr to weak_ptr; it cannot assign new dynamic memory objects to smart pointers. reset will set the pointer to null.

5 dynamic array

Initialize dynamic memory array

int * pia = new int[10];//默认初始化
int * pia2= new int[10]();//值初始化
int * pia3 = new int[10]{1,2,3,4,54,6,7,8,6,5};
  • It is legal to apply for a dynamic array with a size of 0. It is illegal to directly define an array of size 0.

free dynamic array

delete [] pa;

Smart pointers and dynamic arrays

  • unique_ptr dynamic array version
unique_ptr<int[]> up(new int[10]);
up.release();

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-jd3LdgOn-1691409480328)(image/2021-03-06-22-29-22.png)]

5 allocator dynamic memory

Introduction

  • new combines dynamic memory allocation and object construction. That is to say, when allocating dynamic memory, the object on this memory must be determined.
  • Allocator can separate memory allocation and object construction.
  • <memory>in the header file

operate

  • The main operations are as follows
    [External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-DNwuhGXA-1691409480328) (image/2021-03-06-22-34-11.png) ]
allcator<string> alloc;
auto const p = alloc.allocate(n);//分配n个未初始化的string
auto q = p;
alloc.construct(q++);//*q为空字符串
alloc.construct(q++,10,'c');//*q为cccc
alloc.deallocate(p,n)

allocator algorithm

  • The standard library defines two adjoint algorithms for allocators.
    [External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-NENTBvoT-1691409480329)(image/2021-03-06-22-42-35.png)]

allocator principle

  • STL allocators are used to encapsulate the low-level details of memory management for STL containers. In C++, its memory configuration and release are as follows:

  • The new operation is divided into two stages:

    1. Call ::operator new to configure memory;
    2. Call the object constructor to construct the object content
  • The delete operation is divided into two stages:

    1. call object destructor;
    2. Call ::operator delete to release memory
  • For precise division of labor, STL allocator distinguishes two stages of operations: memory allocation is in charge of alloc::allocate(), memory release is in charge of alloc::deallocate(); object construction is in charge of::construct(), and object destruction It is taken care of by ::destroy().

  • At the same time, in order to improve the efficiency of memory management and reduce the memory fragmentation problem caused by applying for small memory, SGI STL uses a two-level configurator. When the allocated space exceeds 128B, the first-level space configurator will be used; When it is less than 128B, the second-level space configurator will be used. The first-level space configurator directly uses malloc(), realloc(), and free() functions to allocate and release memory space, while the second-level space configurator uses memory pool technology to manage memory through free linked lists.

6 Example - simple implementation of allocator

#ifndef __JJALLOC__
#define __JJALLOC__
#endif
#include<new> // for placement new
#include<iostream> //for cerr
#include<cstddef>  //for ptrdiff_t
#include<cstdlib> // for exit()
#include<climits> // for UINT_MAX
namespace my{
    // 申请内存空间。调用operator new 。
    // T*参数是为了注册模板类型T
    template<class T>
    inline T* _allocate(ptrdiff_t size, T*){
        //set_new_handler(0);
        T* tmp = (T*)(::operator new)((size_t)(size * sizeof(T)));
        if (tmp == 0){
            std::cerr << "out of memory" << std::endl;
        }
        return tmp;
    }

    // 释放内存空间。调用operator delete
    template<class T>
    inline void _deallocate(T* buffer){
        ::operator delete(buffer);
    }

    // 创建内存对象。调用placement new
    template<class T1,class T2>
    inline void _construct(T1 *p, const T2 &value){
        new (p)T1(value);
    }
    // 通过查询了解到这个操作叫做placement new,就是在指针p所指向的内存空间创建一个T1类型的对象,但是对象的内容是从T2类型的对象转换过来的(调用了T1的构造函数,T1::T1(value))。
    // 就是在已有空间的基础上重新调整分配的空间,类似于realloc函数。这个操作就是把已有的空间当成一个缓冲区来使用,这样子就减少了分配空间所耗费的时间,因为直接用new操作符分配内存的话,在堆中查找足够大的剩余空间速度是比较慢的。

    // 释放内存对象。调用析构函数。
    template<class T>
    inline void _destroy(T* ptr){
        ptr->~T();
    }


    template <class T>
    class allocate{
    public:
        typedef T value_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef size_t size_type;
        typedef ptrdiff_t difference_type;

        // template<class U>
        // struct rebind{
        //     typedef allocator<U> other;
        // };

        pointer alloc(size_type n, const void * hint = 0){
            return _allocate((difference_type)n, (pointer)0);
        }
        void deallocate(pointer p, size_type n){
            _deallocate(p);
        }

        void construct(pointer p, const_reference value){
            return _construct(p, value);
        }

        void destroy(pointer p){
            _destroy(p);
        }
        pointer address(reference x){
            return (pointer)&x;
        }
        pointer const_address(const_reference x){
            return (const_pointer)&x;
        }

        size_type max_size()const{
            return (size_type)(UINT_MAX / sizeof(T));
        }
    };
}
#include<iostream>
using namespace std;
int main(){
    my::allocate<int> al;
    int * ptr = al.alloc(10);
    cout<<"alloc:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
    al.construct(ptr,123);
    cout<<"construct:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
    al.destroy(ptr);
    cout<<"destroy:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
    al.deallocate(ptr,100);
    cout<<"deallocate:"<<*ptr<<"\t"<<*(ptr+1)<<endl;
    int size = al.max_size();
    cout<<"size:"<<size<<endl;

    int* b=new int[3];
    cout<<*b<<endl;
    new (b)int(999);
    cout<<*b<<endl;
    cout<<*(b+1)<<endl;
}

7 Example - simple implementation of shared_ptr

template <typename T>
class SmartPtr
{
private:
    T *ptr; //底层真实的指针

    int *use_count; //保存当前对象被多少指针引用计数

public:
    SmartPtr(T *p); //SmartPtr<int>p(new int(2));

    SmartPtr(const SmartPtr<T> &orig); //SmartPtr<int>q(p);

    SmartPtr<T> &operator=(const SmartPtr<T> &rhs); //q=p

    ~SmartPtr();

    T operator*(); //为了能把智能指针当成普通指针操作定义解引用操作

    T *operator->(); //定义取成员操作

    T *operator+(int i); //定义指针加一个常数

    int operator-(SmartPtr<T> &t1, SmartPtr<T> &t2); //定义两个指针相减

    void getcount() { return *use_count }
};

int SmartPtr<T>::operator-(SmartPtr<T> &t1, SmartPtr<T> &t2) { 
     return t1.ptr - t2.ptr; 
 }

template <typename T>
SmartPtr<T>::SmartPtr(T *p)
{
    ptr = p;
    try
    {
        use_count = new int(1);
    }
    catch (...)
    {
        delete ptr; //申请失败释放真实指针和引用计数的内存

        ptr = nullptr;
        delete use_count;
        use_count = nullptr;
    }
}
template <typename T>
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig) //复制构造函数

{

    use_count = orig.use_count; //引用计数保存在一块内存,所有的SmarPtr对象的引用计数都指向这里

    this->ptr = orig.ptr;

    ++(*use_count); //当前对象的引用计数加1
}
template <typename T>
SmartPtr<T> &SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
{
    //重载=运算符,例如SmartPtr<int>p,q; p=q;这个语句中,首先给q指向的对象的引用计数加1,因为p重新指向了q所指的对象,所以p需要先给原来的对象的引用计数减1,如果减一后为0,先释放掉p原来指向的内存,然后讲q指向的对象的引用计数加1后赋值给p

    ++*(rhs.use_count);
    if ((--*(use_count)) == 0)
    {
        delete ptr;
        ptr = nullptr;
        delete use_count;
        use_count = nullptr;
    }
    ptr = rhs.ptr;
    *use_count = *(rhs.use_count);
    return *this;
}
template <typename T>
SmartPtr<T>::~SmartPtr()
{
    getcount();
    if (--(*use_count) == 0) //SmartPtr的对象会在其生命周期结束的时候调用其析构函数,在析构函数中检测当前对象的引用计数是不是只有正在结束生命周期的这个SmartPtr引用,如果是,就释放掉,如果不是,就还有其他的SmartPtr引用当前对象,就等待其他的SmartPtr对象在其生命周期结束的时候调用析构函数释放掉

    {
        getcount();
        delete ptr;
        ptr = nullptr;
        delete use_count;
        use_count = nullptr;
    }
}
template <typename T>
T SmartPtr<T>::operator*()
{
    return *ptr;
}
template <typename T>
T *SmartPtr<T>::operator->()
{
    return ptr;
}
template <typename T>
T *SmartPtr<T>::operator+(int i)
{
    T *temp = ptr + i;
    return temp;
}

Guess you like

Origin blog.csdn.net/DeepLearning_/article/details/132153588