[C/C++] 手写一个简单的共享指针

C++ Primer(中文版)第453页,行为像值的类

#include
#include
#include
#include
#include
#include

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(中文版)第455页,行为像指针的类(共享指针)

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

使用 valgrind 工具查看是否有内存泄露

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

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

关于局部变量析构顺序:

局部变量:离开作用域后会被自动析构,析构顺序和声明顺序相反。
局部静态变量:作用域和局部变量一样,但是离开作用域后不会被析构,直到 main 函数结束或者 exit 退出。
全局变量:整个文件中,在 main 之前构造,随着 main 执行完或调用 exit 结束析构
全局静态变量:作用域和全局变量一样

================================================================
关于局部变量在栈中的顺序:

gcc编译默认使用堆栈保护,这导致局部变量先定义后入栈;
如果gcc编译关闭堆栈保护 -fno-stack-protector,局部变量先定义先入栈;

gcc编译32位程序-m32,使用push进行参数压栈(esp自动减4),因此函数调用时,ebp指针上面是参数,下面是局部变量。call会做两件事,一是把返回地址入栈,二是进入函数内部执行指令。return的值通过寄存器返回。
leave指令,这个指令是函数开头的push %ebp和mov %esp,%ebp的逆操作:

  • 把ebp的值赋给esp。
  • 现在esp所指向的栈顶保存着foo函数栈帧的ebp,把这个值恢复给ebp,同时esp增加4。

最后是ret指令,它是call指令的逆操作:

  • 现在esp所指向的栈顶保存着返回地址,把这个值恢复给eip,同时esp增加4,esp的值变成0xbffff3e0。
  • 修改了程序计数器eip,因此跳转到返回地址0x80483c2继续执行。

参考以下

  • https://zhuanlan.zhihu.com/p/103454656
  • https://blog.csdn.net/wangyezi19930928/article/details/16921927

gcc编译64位程序,无参数压栈,参数会通过寄存器传递到函数调用栈的栈顶(rsp上面),rbp指针下面先是局部变量,再是参数。

  • https://z.itpub.net/article/detail/50503CAA1CDDA808A925D5758BD1B0A4

可以使用下面代码进行gdb调试。

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

你可能感兴趣的:(C/C++,c++)