C++策略の書き込み 知識点のC++メモリリリサイクル

作者: リン・ハーン小さな夢, 作成日: 2017-12-29 11:00:02, 更新日: 2017-12-29 11:25:11

C++策略の書き込み 知識点のC++メモリリリサイクル

C++の策略を書く前には,基礎知識が必要であり,コンベクト的な熟練度を求めない.少なくともこれらのルールを知ることである. 下記は転載資料です

  • C++メモリオブジェクト大会

プログラマーのように自称する人がメモリについて何も知らないとすれば,私は彼に言うことができる. CやC++でプログラムを書くとき,メモリにより注意を払う必要があります. これは,メモリの合理的な配分がプログラムの効率性とパフォーマンスに直接影響するだけでなく,特に,私たちがメモリ操作するときに注意せずに問題が発生し,多くの場合,これらの問題は簡単に検出されません. 例えば,メモリ漏洩,例えば,指針の吊り.

C++はメモリを3つの論理領域に分けています. スタック,,静止領域です. そうであるので,それらの領域にあるオブジェクトをスタックオブジェクト,オブジェクト,静止オブジェクトと呼んでいます. では,これらの異なるメモリオブジェクトの違いは何ですか? スタックオブジェクトとオブジェクトの利点とメリットは何ですか? スタックオブジェクトやオブジェクトの作成を禁止するにはどうすればよいですか? これらは今日のテーマです.

  • 1 基本概念

    まず, を見てみましょう.これは,通常,ローカル変数やオブジェクトを保存するために使用されます. 例えば,関数定義では,以下のような文で宣言されるオブジェクト:

    Type stack_object ; 
    

    stack_object は,定義点から始まり,その関数が戻ると終止符が付くキットオブジェクトである.

    また,ほぼすべての仮設オブジェクトはオブジェクトである.例えば,以下の関数定義:

    Type fun(Type object);
    

    この関数は少なくとも2つの一時的なオブジェクトを生成する. まず,参数が値で伝達されるので,コピーコンストラクター関数を呼び出すと一時的なオブジェクト object_copy1を生成する. 函数内の使用はobjectではなく object_copy1である. 自然,object_copy1は,関数が戻ると解放されるのオブジェクトである. また,この関数は,値が返されるので,関数が戻ると,もし返り値最適化を考慮しない場合は,一時的なオブジェクト object_copy2も生成される. この一時的なオブジェクトは,関数が戻った後しばらくの間解放される. 例えば,ある関数には以下のようなコードがあります:

    Type tt ,result ; //生成两个栈对象
    
    tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
    

    上記の第2の文の実行は, fun が返したときに,まず一時的なオブジェクト object_copy2 を生成し,その後,赋值演算子を呼び出す.

    tt = object_copy2 ; //调用赋值运算符
    

    コンパイラが私たちの知覚を無視して,私たちのために多くの仮設オブジェクトを生成し,これらの仮設オブジェクトを生成する時間とスペースのコストは大きくなるかもしれません.

    次に,スタックをご覧ください. スタック,自由記憶区と呼ばれています. これはプログラム実行中に動的に割り当てられているため,その最大の特徴は動態性です. C++では,すべてのスタックオブジェクトの作成と破壊はプログラマが責任を負うため,処理が不適切であればメモリ問題が発生します.

    では,C++ではスタックオブジェクトをどのように割り当てますか?唯一の方法はnew (もちろん,mallocの命令もC型スタックメモリを得ることができます) で,newを使用すると,スタックにメモリを割り当て,そのオブジェクトを指す指針を返します.

    静止領域に戻りましょう. すべての静止オブジェクト,全局オブジェクトは静止領域に割り当てられています. 全局オブジェクトについては,メイン (main) 関数が実行される前に割り当てられています. 実際,メイン (main) 関数の表示コードが実行される前にコンパイラで生成された_main (main) 関数が呼び出され,メイン (main) 関数がすべての全局オブジェクトの構築と初期化作業を行います. そしてメイン (main) 関数が終了する前に,コンパイラで生成された出口関数が呼び出され,すべての全局オブジェクトを解放します.

    void main(void)
    {
        ... // 显式代码
    }
    
    // 实际上转化为这样:
    
    void main(void)
    {
        _main(); //隐式代码,由编译器产生,用以构造所有全局对象
        ...      // 显式代码
        ...
        exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
    }
    

    このことを知れば,ここからいくつかの技を導き出すことができる.例えば, main() の実行の前にいくつかの準備作業を行うと仮定すると,これらの準備作業を,定義された全局オブジェクトのコンストラクターに書き込むことができる.このように,main() の表現コードを実行する前に,この全局オブジェクトのコンストラクターが呼び出され,期待された動作を実行し,私たちの目的を達成する.

    また,静的オブジェクトは,それがクラスの静的メンバーである.この状況を考えると,いくつかの複雑な問題が発生します.

    最初の問題は,classの静止メンバーオブジェクトのライフサイクルです.classの静止メンバーオブジェクトは,最初のclassオブジェクトの生成とともに生成され,プログラム全体の終了時に消滅します.つまり,プログラムではクラスを定義し,そのクラスには静止オブジェクトがメンバーとして存在しますが,プログラム実行中に,このクラスオブジェクトのいずれかを作成しなければ,そのクラスを含む静止オブジェクトも生成されません.また,複数のclassオブジェクトを作成した場合,これらのオブジェクトはすべて静止オブジェクトメンバーを共有します.

    この問題は,次の状況が生じたときです.

    class Base
    {
    public:
        static Type s_object ;
    }
    
    class Derived1 : public Base / / 公共继承
    {
    
        ... // other data 
    
    }
    
    class Derived2 : public Base / / 公共继承
    {
    
        ... // other data 
    
    }
    
    Base example ;
    
    Derivde1 example1 ;
    
    Derivde2 example2 ;
    
    example.s_object = …… ;
    
    example1.s_object = …… ; 
    
    example2.s_object = …… ; 
    

    上記の3つの black box の文に注意してください. s_object がアクセスされているのは同じオブジェクトですか? 答えは確かに同じオブジェクトを指しているということです. これは本当ではありませんよね? しかし,これは本当です. 簡単なコードを書いて自分で確認できます. なぜそうなるのか説明します.

    では,Derived1のオブジェクトを,Base型参数を引用しない関数に渡すときに切断が起こると考えましょう. 切断はどうなるのでしょうか? 知っていると思いますが,Derived1のオブジェクトからsubobjectを取り除いて,Derived1の他のすべてのカスタムデータメンバーを無視して,そのsubobjectを函数に渡すだけです. (実際には,関数はこのsubobjectのコピーを使用しています.)

    BASE を継承するすべての派生型オブジェクトには,Base 型のサブオブジェクトがある (これは,Base 型指針で Derived1 型に指すキーであり,自然にポリモールキーでもある),すべてのサブオブジェクトとすべてのBase 型オブジェクトは同じ s_object を共有している.自然に,Base 型から派生したクラスインスタンス全体のクラスインスタンスが同じ s_object を共有している.上記の例,example 1,example 2 のオブジェクトレイアウトは以下のとおりである:

  • 2 3種類のメモリオブジェクトの比較

    オブジェクトの利点は,適切な時に自動的に生成され,適切な時に自動的に破壊されるので,プログラマが心配する必要はありません.また,オブジェクトの作成は,一般的にスタックオブジェクトよりも速く,スタックオブジェクトを割り当てるとき,operator new操作が呼び出されるため,operator newは,メモリ空間検索アルゴリズムを使用します.この検索プロセスは時間がかかる可能性があります.オブジェクトを生成するには,それほど面倒はありません.それは単に移動する顶指針を必要とします.しかし,注意すべきは,通常空間容量が比較的小さいため,一般的に1MB2MBです.

    スタックオブジェクトは,生成時と破壊時をプログラマが精密に定義するものである.つまり,プログラマがスタックオブジェクトの生命に対する完全なコントロールを持っている.私たちはしばしばそのようなオブジェクトを必要とします.例えば,複数の関数によってアクセスできる一つのオブジェクトを作成したいが,それをグローバルにしたいわけではない場合,この時点でスタックオブジェクトを作成し,その後各関数の間でこのスタックオブジェクトの指針を転送することで,そのオブジェクトの共有を実現することが間違いなく良い選択です.また,キットスペースと比較して,スタック容量ははるかに大きい.実際には,物理メモリが不十分である場合,新しいスタックオブジェクトを生成する必要がある場合,通常実行時にエラーが発生しません.しかし,システムは仮想メモリを使用して実際の物理メモリを拡張します.

    静的オブジェクトをご覧ください.

    まず,グローバルオブジェクトである.グローバルオブジェクトはクラス間通信と関数間通信のための最も簡単な方法を提供しているが,この方法は優雅ではない.一般的に,完全にオブジェクト指向言語では,グローバルオブジェクトは存在しない.例えば,C#では,グローバルオブジェクトは不安全性と高結合を意味し,プログラムの中でグローバルオブジェクトを過剰に使用するとプログラムの強さ,安定性,保守性,複用性を大幅に低下させる.C++でも完全にグローバルオブジェクトを排除できるが,最終的には存在しない.理由の一つは,Cが互換性を持っているからだと思う.

    次に,クラスの静的メンバーである.上記のように,基級およびその派生クラスのすべてのオブジェクトがこの静的メンバーオブジェクトを共有しているので,これらのクラスまたはこれらのクラスオブジェクト間のデータ共有または通信が必要であるときに,このような静的メンバーは間違いなく良い選択である.

    次に静止局所オブジェクトは,主にそのオブジェクトの所在関数が呼び回される間間間状態を保存するために使用される.最も顕著な例は,回帰関数である.回帰関数は,自らを呼び出す関数である.回帰関数の中で非静止局所オブジェクトを定義すると,回帰回数がかなり大きい場合,発生する費用も大きい.これは非静止局所オブジェクトが式オブジェクトであるため,回帰呼び回りごとにこのようなオブジェクトが生成され,戻りごとにこのオブジェクトが解放され,そのようなオブジェクトは,現在の呼び回り層に限定され,より深いネスチャー層とより浅い層の外側には見えない.各層には独自の局所オブジェクトとパラメータがあります.

    回帰関数設計では,静的オブジェクトが非静的局所オブジェクト (すなわちオブジェクト) を置き換えることができる.これは,回帰呼び出しと帰還ごとに非静的オブジェクトを生成し,解放するコストを削減するだけでなく,静的オブジェクトは,回帰呼び出しの中間状態を保存し,各呼び出し層にアクセスできる.

  • 3 を使った意外な収穫

    オブジェクトは適切な時に作成され,適切な時に自動的にリリースされる. つまりオブジェクトには自動管理機能があります. では,オブジェクトが自動的にリリースされるのは何ですか? 第一に,その生命周期が終わり,第二に,その機能が異常なときに.

    オブジェクトは,自動的に解散すると,その独自の解散関数を呼び出す. もし,私たちがオブジェクトにリソースを包装し,またオブジェクトの解散関数で解散の動作を実行すると,資源の漏洩の確率が大幅に減少する. なぜならオブジェクトは,その機能が異常なときでも自動的にリソースを解散できる. 実際のプロセスはこうである. 機能が異常を投げ出すとき, stack_unwindingと呼ばれることが起こる. つまり,スタックの中で展開する. オブジェクトは,自然にの中に存在しているため,オブジェクトの解散関数が実行され,そのために封装された小さなリソースを解放する.

  • 4 スタックオブジェクトの生成を禁止する

    上記のように,あるタイプのスタックオブジェクトを生成することを禁止すると決めた場合,あなたは自己作成のリソースエンパッククラスを作成することができます. このタイプのオブジェクトは,アンパックでのみ生成され,異常な場合に自動的にエンパックされたリソースを解放できます.

    では,スタックオブジェクトを生成することを禁止する方法は? すでに知っているように,スタックオブジェクトを生成する唯一の方法は,new を使用することであり,new を禁止する場合は, new は実行されません. さらに,new は new を実行するときに operator new を呼び出し,operator new は重載可能である.ある方法は,new を private にすることであり,対称性のために,operator をも private に重載することが望ましい.

    #include <stdlib.h> //需要用到C式内存分配函数
    
    class Resource ; //代表需要被封装的资源类
    
    class NoHashObject
    {
    private: 
        Resource* ptr ;//指向被封装的资源
    
        ... ... //其它数据成员
    
        void* operator new(size_t size) //非严格实现,仅作示意之用
        { 
            return malloc(size) ; 
        } 
    
        void operator delete(void* pp) //非严格实现,仅作示意之用
        { 
            free(pp) ; 
        } 
    
    public: 
        NoHashObject() 
        { 
            //此处可以获得需要封装的资源,并让ptr指针指向该资源
    
            ptr = new Resource() ; 
        } 
        
        ~NoHashObject() 
        { 
    
            delete ptr ; //释放封装的资源
        } 
    }; 
    

    NoHashObject は,次のコードを書けば,現在,スタックオブジェクトを禁止するクラスです.

    NoHashObject* fp = new NoHashObject ((() ; //コンパイル期間にエラー!

    fp を削除する.

    上記のコードはコンパイル期間にエラーを生む. さて,今,あなたは,禁止されたスタックオブジェクトのクラスを設計する方法を知っています. 私のような疑問を抱くかもしれません. クラスNoHashObjectの定義が変更できない状態で,そのタイプのスタックオブジェクトを生成することはできませんか?

    void main(void)
    {
        char* temp = new char[sizeof(NoHashObject)] ; 
    
        //强制类型转换,现在ptr是一个指向NoHashObject对象的指针
    
        NoHashObject* obj_ptr = (NoHashObject*)temp ; 
    
        temp = NULL ; //防止通过temp指针修改NoHashObject对象
    
        //再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
    
        Resource* rp = (Resource*)obj_ptr ; 
    
        //初始化obj_ptr指向的NoHashObject对象的ptr成员
    
        rp = new Resource() ; 
    
        //现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
    
        ... ... 
    
        delete rp ;//释放资源
    
        temp = (char*)obj_ptr ; 
    
        obj_ptr = NULL ;//防止悬挂指针产生
    
        delete [] temp ;//释放NoHashObject对象所占的堆空间。
    
        } 
    

    上記の実装は厄介で,この実装はほとんど実用化されていませんが,それを理解することはC++のメモリオブジェクトを理解するのに有益だからです.上記の多くの強制型変換について,最も基本的なことは何ですか?

    記憶中のデータは不変であり,タイプは私たちが着ているメガネであり,メガネを着用すると,我々は異なる解釈によって異なる情報を得るために,対応するタイプでメモリ内のデータを解釈します.

    強制型変換とは,実際に同じメモリデータを別のメガネに置き換えて再表示することである.

    また,異なるコンパイラがオブジェクトのメンバーデータに対するレイアウトを異なる可能性があることも覚えておいてください.例えば,ほとんどのコンパイラがNoHashObjectのptrポインタメンバーをオブジェクトスペースの最初の4バイトに配置することで,以下の文の変換動作が私たちが期待するように実行されることを保証します.

    リソース* rp = (リソース*) obj_ptr ;

    しかし,すべてのコンパイルがそうである必要はありません.

    特定のタイプのスタックオブジェクトを生成することを禁止できるので,オブジェクトを生成できないようにクラスを設計できますか? もちろんできます.

  • 5 の生成を禁止する

    前述したように,のオブジェクトを作成するときに,を適切なサイズに移動してのスペースを移動し,このスペースで直接対応するコンストラクター関数を呼び出してのオブジェクトを形成し,関数が戻ると,その解析式関数を呼び出し,そのオブジェクトを解放し,それからのメモリに戻す.このプロセスは new/delete 操作を必要としないので,operator new/delete を private に設定することは目的を達成しません.もちろん,上記の記述から,あなたは考えていたかもしれません.解析は,コンストラクター関数またはコンストラクター関数をプライベートに設定し,システムが構築式/解析式関数を使用できず,もちろんを生成するオブジェクトにはできません.

    これは可能であり,私もそうするつもりです. しかし,その前に,ある点について考える必要があります. それは,もし私たちがコンストラクター関数をプライベートに設定した場合,newで直接スタックオブジェクトを生成することはできません. newはオブジェクトのスペースを割り当てた後にそのコンストラクター関数を呼び出すからです.

    クラスが基級として意図されていない場合,通常採用する方法は,その解析関数をプライベートとして宣言することです.

    継承を制限せず,オブジェクトを制限するために,解析式関数を protected と宣言することができます.

    class NoStackObject
    {
    protected: 
    
        ~NoStackObject() { } 
    
    public: 
    
        void destroy() 
    
        { 
    
            delete this ;//调用保护析构函数
    
        } 
    }; 
    

    このNoStackObjectクラスは,NoStackObjectのクラスとして,

    NoStackObject* hash_ptr = 新しいNoStackObject() ;

    ...... // hash_ptr を指すオブジェクトに対して操作する

    hash_ptr->destroy ((()); ありがとうございました. newでオブジェクトを作成するときに,deleteで削除するのではなく,destroyで削除する方法があるのは,ちょっと変だと思うか. 明らかに,ユーザーはこの奇妙な方法には慣れていない. だから,私はコンストラクター関数もプライベートまたはprotectedに設定することにしました. これは,先ほど避けようとした問題に戻ります. newを使わずにオブジェクトを作成するには,どのような方法が必要ですか?

    class NoStackObject
    {
    protected: 
    
        NoStackObject() { } 
    
        ~NoStackObject() { } 
    
    public: 
    
        static NoStackObject* creatInstance() 
        { 
            return new NoStackObject() ;//调用保护的构造函数
        } 
    
        void destroy() 
        { 
    
            delete this ;//调用保护的析构函数
    
        } 
    };
    

    このNoStackObjectクラスは,

    NoStackObject* hash_ptr = NoStackObject::creatInstance ((() ";

    ...... // hash_ptr を指すオブジェクトに対して操作する

    hash_ptr->destroy() ;

    hash_ptr = NULL ; // 掛けた指針を使用することを防止する

    現在,物体生成と物体解放の操作が一致しているような感覚は,より良くありませんか.

  • 雑談C++のゴミリサイクル方法

    多くのCまたはC++プログラマは,ゴミ回帰について,ゴミ回帰は自分より動態メモリを管理する方が確実に低効率で,回帰時に必ずプログラムを停止させると考え,メモリ管理を自分でコントロールすれば,割り当てと解き放題時間は安定し,プログラム停止を起こすことはありません.最後に,多くのC/C++プログラマは,C/C++でゴミ回帰メカニズムを実現できないと強く信じています.これらの誤った考えは,ゴミ回帰アルゴリズムを理解していないことから推測されています.

    実際,ゴミリサイクルメカニズムは遅くないし,ダイナミックメモリ配分よりも効率的だ. なぜなら,我々は配分しかできないので,配分メモリでは,スタックから常に新しいメモリを取得するだけで,移動するスタックの指針が十分である. そして,解放プロセスは省略され,自然に加速されている.

    ゴミ処理のアルゴリズムは,通常,現在使用可能なすべてのメモリブロックをスキャンし,マークし,割り当てられたすべてのメモリから未指定メモリをリサイクルする.C/C++では,ゴミ処理が実現できないという見解は,通常,可能で使用されるすべてのメモリブロックを正しくスキャンできないという見解に基づいている.しかし,実際は,不可能に見えることが実現されるのは複雑ではない.まず,メモリをスキャンすることで,スタックに動的に割り当てられたメモリに指す針は容易に識別され,認識の誤差がある場合,単に指針ではないデータを指針として,指針を非指針データとして指針として使用することのみが可能です.このように,ゴミ処理のプロセスでは,誤ったデータを削除する誤ったメモリ機能をリサイクルするだけで,誤ったものを削除します.次に,すべての記憶されたブロックに参照される場合は,すべての内部,内部,内部,および全体的な変化が存在します.

    ゴミ処理では,bss段,データ段,および現在使用されているキットスペースをスキャンして,動的メモリ指針となる可能性のある量のデータを検索し,参照されたメモリを回転スキャンすると,現在使用されているすべての動的メモリが表示されます.

    プロジェクトのために良いゴミ処理機を導入すれば,メモリ管理の速度を向上させ,全体的なメモリ消費を削減することも可能である.興味がある場合は,オンラインで既に利用されているゴミ処理に関する論文や実装されたライブラリを検索して,開拓的な視野を持つことがプログラマにとって特に重要です.

    翻訳されたHK ジャン

  • なぜ,局所変数のアドレスが指針に与えられれば,そのライフサイクルはプログラム全体の終了まで延長されるのでしょうか?

    #include<stdio.h>
    int*fun(){
        int k = 12;
        return &k;
    }
    int main(){
        int *p = fun();    
        printf("%d\n", *p);
    
        getchar();
        return 0;
    }
    

    しかし,このアクセスが不確実であるとは限りません. 局所変数のアドレスはすべてのプログラム自身のスタックにあり,当局変数の終了後,局所変数のメモリアドレスを他の変数に代入しない限り,その値は存在します.しかし,変更された場合,このメモリアドレスはプログラムの他の変数に代入され,指針で強制的に変更された場合,プログラムクラッシュを引き起こす可能性があるため,比較的危険です.

    csdn bbs について


もっと