c/c++ポインタエッセンス2(1)


1.3ポインタとメモリ管理ポインタを使用して、メモリ内の任意の場所にデータを書き込むことができますが、プログラムに野ポインタがあると(「wild」pointer)つまり、誤った位置を指すポインタは、スタックに格納されているデータが破壊される可能性があり、スタックを管理するためのデータ構造が破壊される可能性があり、オペレーティングシステムのデータも修正される可能性があり、上記の3つの破壊が同時に発生する場合があります.したがって,合理的で正確な割り当てポインタのアドレスは非常に重要である.1.3.1メモリ割当て方式メモリ割当て方式は,(1)静的記憶領域からの割当ての3つである.メモリはプログラムのコンパイル時にすでに割り当てられており、この内にプログラムが存在する全実行期間が存在します.たとえばグローバル変数、static変数です.(2)スタック上に作成する.関数を実行すると、関数内のローカル変数のメモリセルがスタック上に作成され、関数の実行が終了すると、これらのメモリセルが自動的に解放されます.スタックメモリ割り当て演算はプロセッサの命令セットに組み込まれており,効率は高いが,割り当てられたメモリ容量は限られている.(3)スタックからの割当て,動的メモリ割当てとも呼ばれる.プログラムは実行時にmallocまたはnewで任意のメモリ数を申請し、プログラマー自身がfreeまたはdeleteでメモリをいつ解放するかを担当します.ダイナミックメモリの生存期間は、非常に柔軟な使用が決定されますが、問題も最も多く、ダイナミックメモリの割り当てに重点を置いて説明します.1.3.2 malloc/freeの使用要点mallocとfreeは、動的メモリの申請およびメモリの解放に使用されるC/C++言語の標準ライブラリ関数です.関数mallocのプロトタイプは、void*malloc(size_t size);長さlengthの整数タイプのメモリをmallocで申請します.プログラムはint*ip=(int*)malloc(sizeof(int)*length);「タイプ変換」と「sizeof」の2つの要素に集中しなければならない.malloc関数の戻り値のタイプはvoid*なので、mallocを呼び出すときに明示的にタイプ変換を行い、void*を必要なポインタタイプに変換します.malloc関数自体は、申請するメモリのタイプを認識せず、メモリの合計バイト数にのみ関心を持っています.例えばint変数は16ビットシステムでは2バイト、32ビットでは4バイトである.float変数は16ビットシステムでは4バイト,32ビットでは4バイトである.これはsizeof(タイプ)でテストすることができます.mallocの「()」でsizeof演算子を使うのは良いスタイルですが、時々頭がぼんやりしてip=malloc(sizeof(ip))というプログラムを書くことに注意しましょう.関数freeの原型は以下の通りである:void free(void*memblock);なぜfree関数はmalloc関数ほど複雑ではないのでしょうか.これは,ポインタpのタイプとその指すメモリの容量が事前に知られており,文free(p)がメモリを正しく解放できるからである.pがNULLポインタの場合、free対pは何度操作しても問題ありません.pがNULLポインタでない場合、freeがpを2回連続して操作するとプログラムの実行エラーが発生します.1.3.3 new/deleteの使用要点内部データ型以外のオブジェクトでは、maloc/freeだけではダイナミックオブジェクトの要件を満たすことができません.オブジェクトは作成と同時にコンストラクション関数を自動的に実行し、オブジェクトは消滅する前にコンストラクション関数を自動的に実行します.malloc/freeは演算子ではなくライブラリ関数であるため、コンパイラ制御権限内ではなく、コンストラクション関数とコンストラクション関数を実行するタスクをmalloc/freeに押し付けることはできません.したがって、C++言語には、動的メモリ割り当てと初期化作業を完了できる演算子newと、メモリのクリーンアップと解放作業を完了できる演算子deleteが必要です.注意new/deleteはライブラリ関数ではなく、C++の演算子です.次の例を見てみると、何が起こっているのか分かります.
Cコード
  • class Object   
  •   
  • {   
  •   
  •  public :   
  •   
  •   Object(void){std::cout << “Initialization”<< std::endl; }   
  •   
  •   ~Object(void){std::cout << “Destroy”<< std::endl; }   
  •   
  •   void Initialize(void){std:: cout << “Initialization”<< std::endl; }   
  •   
  •   void Destroy(void){ std::cout << “Destroy”<< std::endl; }   
  •   
  • }   
  •   
  • void UseMallocFree(void)   
  •   
  • {   
  •   
  •  Object *ip = (Object *)malloc(sizeof(Object));//動的メモリ申請
  •   
  •  ip->Initialize();//初期化
  •   
  •  //…   
  •   
  •  ip->Destroy();//パージ作業
  •   
  •  free(ip);//メモリを解放する
  •   
  • }   
  •   
  • void UseNewDelete(void)   
  •   
  • {   
  •   
  •  Object *ip = new Object;//動的メモリを申請し、
  • を初期化する
  •   
  •  //…   
  •   
  •  Delete ip;//メモリ
  • をクリアして解放する
  •   
  • }  
  • class Object
    
    {
    
     public :
    
      Object(void){std::cout << “Initialization”<< std::endl; }
    
      ~Object(void){std::cout << “Destroy”<< std::endl; }
    
      void Initialize(void){std:: cout << “Initialization”<< std::endl; }
    
      void Destroy(void){ std::cout << “Destroy”<< std::endl; }
    
    }
    
    void UseMallocFree(void)
    
    {
    
     Object *ip = (Object *)malloc(sizeof(Object));    //       
    
     ip->Initialize();                             //    
    
     //…
    
     ip->Destroy();                              //     
    
     free(ip);                                   //     
    
    }
    
    void UseNewDelete(void)
    
    {
    
     Object *ip = new Object;                     //            
    
     //…
    
     Delete ip;                                  //         
    
    }

    オブジェクトの動的メモリ管理クラスObjectの関数Initializeをmalloc/freeとnew/deleteでどのように実現するかについて,コンストラクション関数の機能をシミュレートし,関数Destroyはコンストラクション関数の機能をシミュレートした.関数UseMallocFreeでは、malloc/freeではコンストラクション関数と解析関数を実行できないため、メンバー関数InitializeとDestroyを呼び出して初期化とパージを完了する必要があります.関数UseNewDeleteはずっと簡単です.したがって、malloc/freeを使用してダイナミックオブジェクトのメモリ管理を完了しようとしないで、new/deleteを使用します.内部データ型の「オブジェクト」には、malloc/freeおよびnew/deleteは構造および解析のプロセスがないため、等価です.Newにはsizeof、タイプ変換、タイプセキュリティチェック機能が内蔵されており、内部データ型以外のオブジェクトでは、newはダイナミックオブジェクトを作成しながら初期化を完了します.新/deleteでよく使われる方法は、typeof*ip=new typeof[length];クラス/構造*ip=newクラス構造;一般的にはdelete ip;配列の解放は以下の通りである:delete[]ip;1.3.4メモリが切れたらどうする?ダイナミックメモリを申請するときに十分なメモリブロックが見つからない場合、mallocとnewはNULLポインタを返し、メモリ申請に失敗したことを宣言します.通常、メモリ消費量の問題は3つの方法で処理されます.(1)ポインタがNULLであるか否かを判断し,そうであれば直ちにreturn文で本関数を終了する.例:
    Cコード
  • void Func(void)   
  •   
  • {   
  •   
  •  A *a = new A;   
  •   
  •  if(a == NULL)   
  •   
  •  {   
  •   
  •   return;   
  •   
  •  }   
  •   
  •  …   
  •   
  • }  
  • void Func(void)
    
    {
    
     A *a = new A;
    
     if(a == NULL)
    
     {
    
      return;
    
     }
    
     …
    
    }

    (2)ポインタがNULLであるか否かを判断し,もしそうであれば直ちにexit(1)でプログラム全体の実行を終了する.例:
    Cコード
  • void Func(void)   
  •   
  • {   
  •   
  •  A *a = new A;   
  •   
  •  if(a == NULL)   
  •   
  •  {   
  •   
  •   std::cout << “Memory Exhausted” << std::endl;   
  •   
  •   exit(1);   
  •   
  •  }   
  •   
  •  …   
  •   
  • }  
  • void Func(void)
    
    {
    
     A *a = new A;
    
     if(a == NULL)
    
     {
    
      std::cout << “Memory Exhausted” << std::endl;
    
      exit(1);
    
     }
    
     …
    
    }

    (3)newとmallocに異常処理関数を設定する.例えばVisual C++が使えますset_new_hander関数はnewにユーザ自身が定義した異常処理関数を設定し,mallocにnewと同じ異常処理関数を享受させることもできる.詳細はC++マニュアルを参照してください.皆さんに伝えたい重要な現象があります.32ビット以上のアプリケーションでは、mallocとnewをどのように使用しても「メモリ消費」を招くことはほとんどありません.32ビットオペレーティングシステムが「ダミーメモリ」をサポートしているため、メモリが切れ、自動的にハードディスク(HDD)領域で置き換えられます.私は読者を誤導したくない.誤った処理をしないとプログラムの品質が悪くなり、決して小さなミスで大きくしてはいけないと強調しなければならない.