ポインタに関する心得のポインタよくあるエラー

5298 ワード

変換元:http://www.eefocus.com/zhangjingbin/blog/10-05/190629_014f8.html
1.よくあるメモリエラーと対策
      メモリエラーが発生するのは非常に面倒なことです.コンパイラはこれらのエラーを自動的に発見することはできません.通常、プログラムの実行時にスナップできます.これらの間違いの多くは明らかな症状がなく、時々隠れて現れ、間違いを直す難しさを増している.时にはユーザーが怒ってあなたを探してきたが、プログラムには何の問題も発生しなかった.あなたが行くと、間違いがまた発生した.よくあるメモリエラーとその対策は次のとおりです.    (1)メモリ割り当てに失敗したのに使用した.     プログラミング初心者は、メモリの割り当てが成功しないことに気づかなかったため、このようなエラーをよく犯します.一般的な解決策は、メモリを使用する前にポインタがNULLであるかどうかを確認することです.ポインタpが関数のパラメータであれば,関数の入口でassert(p!=NULL)でチェックする.mallocまたはnewでメモリを申請する場合はif(p=NULL)またはif(p!=NULL)でエラー防止処理を行うべきです.    (2)メモリ割り当ては成功したが,初期化されていないので参照する.      このような間違いを犯すのは主に2つの起因がある:1つは初期化の観念がないことである.2つ目は、メモリのデフォルトの初期値がすべてゼロであると勘違いし、配列などの参照初期値エラーを引き起こすことです.メモリのデフォルトの初期値は何なのか、統一された基準がありません.ゼロの場合もありますが、私たちはそれを信じています.したがって,配列をどのように作成しても初期値を付与することを忘れず,ゼロ値を付与しても省略することはできず,面倒をいとわない.    (3)メモリ割り当ては成功し初期化されたが,操作はメモリの境界を越えた.      たとえば配列を使用する場合、下付きの「複数1」または「少ない1」の操作が頻繁に発生します.特にforループ文では,ループ回数が誤りやすく,配列操作が境界を越えてしまう.    (4)メモリを解放するのを忘れ、メモリが漏れる.     このエラーを含む関数は、呼び出されるたびにメモリが失われます.最初はシステムのメモリが十分で、エラーは見えませんでした.結局、プログラムが突然死んで、システムにメモリが切れたというヒントが表示されました.動的メモリの申請と解放はペアでなければならない.プログラム中のmallocとfreeの使用回数は必ず同じでなければならない.そうしないと、間違いがあるに違いない(new/delete同理).
    (5)メモリを解放しても使用し続ける(いわゆる野ポインタ).一般的には、ポインタを外してからポインタを空にします.
2.ポインタと配列の比較
      C++/Cプログラムでは,ポインタと配列が多くの場所で互いに置き換えられ,両者が等価であると錯覚する.配列は、グローバル配列などの静的ストレージ領域に作成されるか、スタックに作成されます.配列名は(指向ではなく)メモリに対応し、そのアドレスと容量はライフタイム内に変わらず、配列の内容だけが変更できます.ポインタは、「可変」を特徴とする任意のタイプのメモリブロックをいつでも指すことができるので、ダイナミックメモリを操作するためにポインタをよく使用します.ポインタは配列よりはるかに柔軟ですが、危険です.
例1:
     文字配列aの容量は6文字であり、その内容はhelloである.aの内容は、a[0]='X'のように変更することができる.ポインタpは定数文字列「world」(静的記憶領域にあり、内容はworld)を指し、定数文字列の内容は変更できない.構文的には、コンパイラは文p[0]='X'に何か不適切なものがあるとは思わないが、この文が定数文字列の内容を修正しようとしたため、実行エラーが発生した.
          char a[] = “hello”;
          a[0] = 'X’;
          cout << a << endl;
          char *p = “world”; //   p       
          p[0] = 'X’; //           
          cout << p << endl;      

例2:
演算子sizeofを用いて配列の容量(バイト数)を算出することができる.注意配列が関数のパラメータとして伝達されると、その配列は自動的に同型のポインタに劣化する.配列aの容量にかかわらずsizeof(a)は常にポインタの容量に等しい
 char a[] = "hello world";
 char *p = a;
 cout<< sizeof(a) << endl; // 12  (          ‘\0’)
 cout<< sizeof(p) << endl; // 4         3.3(a)             

 void Func(char a[100])
 {
      cout<< sizeof(a) << endl; // 4     100  
 }

3.ポインタパラメータ転送メモリ
    関数で値を返してダイナミックメモリを渡す方法は使いやすいが、return文を間違って使う人が多い.(次の使い方は正しい)
char *GetMemory3(int num)
{
    char *p = (char *)malloc(sizeof(char) * num);
    return p;
}
void Test3(void)
{
    char *str = NULL;
    str = GetMemory3(100);
    strcpy(str, "hello");
    cout<< str << endl;
    free(str);
}   

      ここでは、関数の終了時に自動的にメモリが消滅するため、return文で「スタックメモリ」へのポインタを返さないことを強調します.(次は間違いです)
char *GetString(void)
{
    char p[] = "hello world";
    return p; //         
}
void Test4(void)
{
    char *str = NULL;
    str = GetString(); // str       
    cout<< str << endl;
}   

テスタでTest 4を逐次追跡したところ、str=GetString文を実行するとstrはNULLポインタではないが、strの内容は「hello world」ではなくゴミであることが分かった.
例を次のように変更すると
char *GetString2(void)
{
    char *p = "hello world";
    return p;
}
void Test5(void)
{
    char *str = NULL;
    str = GetString2();
    cout<< str << endl;
} 

    関数Test 5の実行はエラーはありませんが、関数GetString 2の設計概念はエラーです.GetString 2内の「hello world」は定数文字列であり、静的記憶領域に位置し、プログラムのライフサイクル中は一定であるためです.GetString 2は、いつ呼び出されても、常に同じ読み取り専用メモリブロックを返します.
4.野指針の根絶
     野ポインタはNULLポインタではなく、「ゴミ」メモリへのポインタです.一般的にNULLポインタを誤用することはありません.if文で判断しやすいからです.しかし、「野ポインタ」は危険で、if文は役に立たない.「野指針」の成因は主に2つあります.
     (1)ポインタ変数は初期化されていない.任意のポインタ変数は作成されたばかりのときに自動的にNULLポインタにはなりません.デフォルト値はランダムで、むやみに指を指します.したがって、ポインタ変数は作成と同時に初期化されるべきであり、ポインタをNULLに設定するか、合法的なメモリを指すようにします.たとえば               char *p = NULL;                char *str = (char *) malloc(100);
               //次は不法です
               char *str;
                scanf("%s",str);//strは初期値を与えず、未知のアドレス空間を指しているので、これはできません.
               //でも次はこれでいい
               char str[10];//コンパイル時にメモリ領域が割り当てられ、特定のアドレスがあります.
                scanf("%s",str);
               //同じように次のようにしてもいいです.
               char str[10],*a;
               a=str;
               scanf("%s",a);      (2)ポインタpがfreeまたはdeleteされた後,NULLとして置かれず,pが合法的なポインタであると勘違いされる.     (3)ポインタ操作は変数の作用範囲を超えている.このような状況は防ぎきれない.例のプログラムは以下の通りである.
class A
{
public:
    void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
    A *p;
   {
    A a;
    p = &a; //    a     
    }
    p->Func(); // p “   ”
}

        関数Testは文p->Func()を実行するとオブジェクトaが消え,pはaを指すのでpは「野ポインタ」となる.しかし、不思議なことに、私がこのプログラムを実行したときにエラーがなかったのは、コンパイラと関係があるかもしれません.