[C/C++] Write a simple shared pointer by hand

C++ Primer (Chinese version) page 453, Classes that behave like values

#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
#include<limits.h>
#include<memory>

using namespace std;

class Hello {
    
    
public:
    explicit Hello(const string &s) : ps(new string(s)), i(5) {
    
    }
    Hello(const Hello &ins) : ps(new string(*ins.ps)), i(ins.i) {
    
    }
    Hello& operator=(const Hello&);
private:
    string *ps;
    int i;
};

// self-assignment
Hello& Hello::operator=(const Hello &rhs) {
    
    
    string *newp = new string(*rhs.ps);
    delete ps;
    ps = newp;
    i = rhs.i;
    return *this;
}

C++ Primer (Chinese version) page 455, Classes that behave like pointers (shared pointers)

template<class T>
class MyPtr
{
    
    
private:
    T *ptr;
    size_t *use;
public:
    MyPtr() : ptr(nullptr), use(new size_t(1)) {
    
    }
    explicit MyPtr(T *t) : ptr(t), use(new size_t(1)) {
    
    }
    MyPtr(const MyPtr<T> &ins) : ptr(ins.ptr), use(ins.use) {
    
    
        ++*use;
    }
    MyPtr<T>& operator=(const MyPtr<T> &rhs);
    T& operator*() {
    
    
        return *ptr;
    }
    T* operator->() {
    
    
        return ptr;
    }
    ~MyPtr();
};

template<class T>
MyPtr<T> mymake_shared(const T &t) {
    
    
    return MyPtr<T>(new T(t));
}

template<class T>
MyPtr<T>& MyPtr<T>::operator=(const MyPtr<T> &rhs) {
    
    
    ++*rhs.use;
    --*use;
    if (*use == 0) {
    
    
        delete ptr;
        delete use;
    }
    use = rhs.use;
    ptr = rhs.ptr;
    return *this;
}

template<class T>
MyPtr<T>::~MyPtr() {
    
    
    // --*use;
    if (--*use == 0) {
    
    
        delete ptr;
        delete use;
    }
}

int main() {
    
    
    MyPtr<string> p1 = mymake_shared<string>("12121");
    MyPtr<string> p2(p1);
    p2 = p1;
    cout << *p1 << "\n" << p1->back() << endl;
    // 开 debug,可以发现 return 语句之后会自动析构 p2 和 p1,不会有内存泄露
    return 0;
}

Use the valgrind tool to check for memory leaks

valgrind --tool=memcheck --leak-check=full ./hello

================================================================

Regarding the order of local variable destruction:

Local variables: After leaving the scope, they will be automatically destructed, and the order of destruction is opposite to the order of declaration.
Local static variables: the scope is the same as local variables, but they will not be destructed after leaving the scope until the end of the main function or exit.
Global variables: Constructed before main in the entire file, and destructed after main is executed or exit is called
Global static variables: The scope is the same as that of global variables

==================================================== ===============
About the order of local variables in the stack:

By default, gcc compilation uses stack protection , which causes local variables to be defined first and then pushed onto the stack;
if gcc compiles with stack protection turned off -fno-stack-protector, local variables are defined first and then pushed onto the stack;

gcc compiles a 32-bit program-m32 , and uses push to push parameters onto the stack (esp is automatically decremented by 4), so when the function is called, the ebp pointer is above the parameter, and below it is the local variable. call will do two things, one is to push the return address onto the stack, and the other is to enter the function to execute instructions. The value of return is returned via the register.
The leave instruction, this instruction is the inverse operation of push %ebp and mov %esp, %ebp at the beginning of the function:

  • Assign the value of ebp to esp.
  • Now the top of the stack pointed to by esp holds the ebp of the stack frame of the foo function, restore this value to ebp, and increase esp by 4.

Finally there is the ret instruction, which is the inverse of the call instruction:

  • Now the top of the stack pointed to by esp holds the return address, restore this value to eip, and increase esp by 4, and the value of esp becomes 0xbffff3e0.
  • The program counter eip is modified, so it jumps to the return address 0x80483c2 to continue execution.

Refer to the following

gcc compiles a 64-bit program, pushes the stack without parameters, and the parameters will be passed to the top of the function call stack (above rsp) through registers. Below the rbp pointer are local variables first, and then parameters.

You can use the following code for gdb debugging.

gcc -g -m32 -o hello hello.c
gdb ./hello
>(gdb) disassemble main
>(gdb) disassemble caller
>(gdb) disassemble swap_add
int swap_add(int *xp, int *yp)
{
    
    
    int x = *xp;
    int y = *yp;
    *xp = y;
    *yp = x;
    return x + y;
}

int caller(int x, int y)
{
    
    
    int arg1 = x;
    int arg2 = y;
    int sum = swap_add(&arg2, &arg1);
    int diff = arg1 - arg2;
    return sum * diff;
}

int main() {
    
    
    const char *p1 = "121212";
    const char *p2 = "324345";
    int x = 1;
    int y = 2;
    int z = 3;
    caller(x, y);
    int m = 10;
    int n = 12;
    return 0;
}

Guess you like

Origin blog.csdn.net/weixin_43742643/article/details/129968152