高品質C+/Cプログラミング学習ノート(三)---メモリ管理(1)
1、3種類のメモリ割り当て方式
これは最も基本的な知識です.
(1)静的記憶領域からの割当て.メモリはプログラムのコンパイル時にすでに割り当てられており、この内にプログラムが存在する全実行期間が存在します.たとえばグローバル変数、static変数です.(2)スタック上に作成する.関数を実行すると、関数内のローカル変数のメモリセルがスタック上に作成され、関数の実行が終了すると、これらのメモリセルが自動的に解放されます.スタックメモリ割り当て演算はプロセッサの命令セットに組み込まれており,効率は高いが,割り当てられたメモリ容量は限られている.(3)スタックからの割当て,動的メモリ割当てとも呼ばれる.プログラムは実行時にmallocまたはnewで任意のメモリ数を申請し、プログラマー自身がfreeまたはdeleteでメモリをいつ解放するかを担当します.ダイナミックメモリの生存期間は、柔軟性に優れていますが、最も問題があります.
2、間違いやすい点
(1)メモリ割り当てに失敗したのに使用した.
使用前にNULLかどうかを確認し、関数でassertを使用してassert(p!=NULL)を判断します.
(2)メモリ割り当ては成功したが,初期化されていないので参照する.
(3)メモリ割り当ては成功し初期化されたが,操作はメモリの境界を越えた.最も一般的なのは配列です.
(4)メモリを解放するのを忘れ、メモリが漏れる.
ダイナミックメモリの申請とリリースはペアでなければなりません.malloc後にfreeを覚え、new後にdeleteを覚えます.
(5)メモリを解放しても引き続き使用する.発見しにくいエラーの1つは、returnでスタックメモリを指すポインタまたはリファレンスを返すことですが、メモリは関数体の終了時に自動的に破棄されます.
(6)freeまたはdeleteを使用してメモリを解放した後、ポインタをNULLに設定します.そうしないと、「野ポインタ」が発生します.
(7)配列と動的メモリに初期値を付けることを忘れず,初期化されていないメモリを右値として使用しないようにする.
3、ポインタと配列
ポインタと配列は等価だと思っている人が多いが、そうではない.
配列は、グローバル配列などの静的ストレージ領域に作成されるか、スタックに作成されます.配列名は(指向ではなく)メモリに対応し、そのアドレスと容量はライフタイム内に変わらず、配列の内容だけが変更できます.ポインタは、「可変」を特徴とする任意のタイプのメモリブロックをいつでも指すことができるので、ダイナミックメモリを操作するためにポインタをよく使用します.ポインタは配列よりはるかに柔軟ですが、危険です.
以上の例から分かるように、ポインタpは定数文字列「world」(静的記憶領域にあり、内容はworld0)を指し、定数文字列の内容は修正できない.
コンパイルエラーが発生するため、配列aの内容をb=aで配列bに割り当てることはできない.strcpyでコピーする必要があります.
if(p=a)比較はコンテンツではなくアドレスであり,ライブラリ関数strcmpで比較すべきである.
以下はポインタとこれらの点での違いです.
4、メモリ容量を計算する.
この点は面接でたくさんの人を倒すことができます.
sizeofは、'0'を含む配列の容量(バイト数)を計算することができる.パラメータがポインタであれば、ポインタの長さ:4を返します.C++/C言語では、メモリの申請時に記憶しない限り、ポインタが指すメモリ容量を知ることはできません.
もう1つ:配列が関数のパラメータとして伝達されると、その配列は自動的に同じタイプのポインタに劣化します.いくつかの例を見てみましょう.
これは最も基本的な知識です.
(1)静的記憶領域からの割当て.メモリはプログラムのコンパイル時にすでに割り当てられており、この内にプログラムが存在する全実行期間が存在します.たとえばグローバル変数、static変数です.(2)スタック上に作成する.関数を実行すると、関数内のローカル変数のメモリセルがスタック上に作成され、関数の実行が終了すると、これらのメモリセルが自動的に解放されます.スタックメモリ割り当て演算はプロセッサの命令セットに組み込まれており,効率は高いが,割り当てられたメモリ容量は限られている.(3)スタックからの割当て,動的メモリ割当てとも呼ばれる.プログラムは実行時にmallocまたはnewで任意のメモリ数を申請し、プログラマー自身がfreeまたはdeleteでメモリをいつ解放するかを担当します.ダイナミックメモリの生存期間は、柔軟性に優れていますが、最も問題があります.
2、間違いやすい点
(1)メモリ割り当てに失敗したのに使用した.
使用前にNULLかどうかを確認し、関数でassertを使用してassert(p!=NULL)を判断します.
(2)メモリ割り当ては成功したが,初期化されていないので参照する.
(3)メモリ割り当ては成功し初期化されたが,操作はメモリの境界を越えた.最も一般的なのは配列です.
(4)メモリを解放するのを忘れ、メモリが漏れる.
ダイナミックメモリの申請とリリースはペアでなければなりません.malloc後にfreeを覚え、new後にdeleteを覚えます.
(5)メモリを解放しても引き続き使用する.発見しにくいエラーの1つは、returnでスタックメモリを指すポインタまたはリファレンスを返すことですが、メモリは関数体の終了時に自動的に破棄されます.
(6)freeまたはdeleteを使用してメモリを解放した後、ポインタをNULLに設定します.そうしないと、「野ポインタ」が発生します.
(7)配列と動的メモリに初期値を付けることを忘れず,初期化されていないメモリを右値として使用しないようにする.
3、ポインタと配列
ポインタと配列は等価だと思っている人が多いが、そうではない.
配列は、グローバル配列などの静的ストレージ領域に作成されるか、スタックに作成されます.配列名は(指向ではなく)メモリに対応し、そのアドレスと容量はライフタイム内に変わらず、配列の内容だけが変更できます.ポインタは、「可変」を特徴とする任意のタイプのメモリブロックをいつでも指すことができるので、ダイナミックメモリを操作するためにポインタをよく使用します.ポインタは配列よりはるかに柔軟ですが、危険です.
char a[] = “hello”;
a[0] = ‘X’;
cout << a << endl;
char *p = “world”; // p
p[0] = ‘X’; //
cout << p << endl;
以上の例から分かるように、ポインタpは定数文字列「world」(静的記憶領域にあり、内容はworld0)を指し、定数文字列の内容は修正できない.
コンパイルエラーが発生するため、配列aの内容をb=aで配列bに割り当てることはできない.strcpyでコピーする必要があります.
if(p=a)比較はコンテンツではなくアドレスであり,ライブラリ関数strcmpで比較すべきである.
以下はポインタとこれらの点での違いです.
// …
char a[] = "hello";
char b[10];
strcpy(b, a); // b = a;
if(strcmp(b, a) == 0) // if (b == a)
…
// …
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // p = a;
if(strcmp(p, a) == 0) // if (p == a)
…
4、メモリ容量を計算する.
この点は面接でたくさんの人を倒すことができます.
sizeofは、'0'を含む配列の容量(バイト数)を計算することができる.パラメータがポインタであれば、ポインタの長さ:4を返します.C++/C言語では、メモリの申請時に記憶しない限り、ポインタが指すメモリ容量を知ることはできません.
もう1つ:配列が関数のパラメータとして伝達されると、その配列は自動的に同じタイプのポインタに劣化します.いくつかの例を見てみましょう.
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12
cout<< sizeof(p) << endl; // 4
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 100
}