【C/C++】シンプルな共有ポインタを手で書く

C++ Primer (中国語版) 453 ページ、値のように動作するクラス

#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 (中国語版) 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 の前に構築され、main の実行または exit が呼び出された後に破棄されます
グローバル静的変数: スコープはグローバル変数のスコープと同じです

================================================== == ===============
スタック内のローカル変数の順序について:

デフォルトでは、 gcc コンパイルはスタック保護を使用します。これにより、最初にローカル変数が定義されてからスタックにプッシュされます。
スタック保護をオフにして gcc をコンパイルする-fno-stack-protectorと、ローカル変数が最初に定義されてからスタックにプッシュされます。

gcc は 32 ビット プログラムをコンパイルし-m32、push を使用してパラメーターをスタックにプッシュします (esp は自動的に 4 ずつ減分されます)。そのため、関数が呼び出されると、ebp ポインターはパラメーターの上にあり、その下にローカル変数があります。call は 2 つのことを行います。1 つは戻りアドレスをスタックにプッシュすることで、もう 1 つは関数に入って命令を実行することです。return の値はレジスタを介して返されます。
leave 命令、この命令は、関数の先頭にある push %ebp と mov %esp, %ebp の逆操作です。

  • ebp の値を esp に割り当てます。
  • esp が指すスタックの一番上に foo 関数のスタック フレームの ebp が保持され、この値を ebp に戻し、esp を 4 増やします。

最後に、call 命令の逆である ret 命令があります。

  • esp が指すスタックの一番上に戻りアドレスが保持され、この値を eip に戻し、esp を 4 増やすと、esp の値は 0xbffff3e0 になります。
  • プログラム カウンター eip が変更されるため、リターン アドレス 0x80483c2 にジャンプして実行を継続します。

以下を参照してください

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. rbp ポインターの下には、最初にローカル変数があり、次にパラメーターがあります。

次のコードを 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;
}

おすすめ

転載: blog.csdn.net/weixin_43742643/article/details/129968152