記事ディレクトリ
序文
記事のタイトルは「動的メモリ管理」ですが、動的メモリ管理とは何ですか?なぜ動的メモリ管理があるのでしょうか?
これまでに学んだ知識を振り返ると、記憶を開くために習得した方法は次のとおりです。
char ch = 'a';
int val = 10;
float f_val = 12.8f;
int arr[20] = {
0 };
//....
メモリを解放する上記の方法には、いくつかの欠点があります。
- 開いたスペースのサイズは固定されています
- 配列を宣言する場合、配列の長さを指定する必要がありますが、配列空間のサイズが決定されると、それを調整することはできません。
しかし、スペースの需要は上記の状況だけではありません。必要なスペースの量はプログラムの実行中にしかわからない場合があるため、コンパイル時に配列にスペースを割り当てる方法は満足のいくものではありません。
C 言語では動的メモリ割り当てが導入されており、プログラマが自分で領域を申請したり解放したりできるため、より柔軟になります。
1.malloc
この関数の機能は次のとおりです。 メモリ ブロックを 連続メモリ スペースに適用し、戻るスペースの開始アドレスへのポインタポインタ。
- スペースの場合
开辟成功
は、スペースの開始アドレスへのポインタを返します。 - スペースの場合
开辟失败
、NULL ポインタが返されます。したがって、 関数の戻り値をチェックする必要があります - 開いたスペースは初期化されません。
- 戻り値の型は void* であるため、関数は開かれるスペースの種類を知りません。スペースを必要とするユーザーが自分で決定します。
- 関数の引数が0の場合、戻り値は不定となりコンパイラに依存します。
- ヘッダー ファイル stdlib.h
malloc 関数を使用して 40 バイトのスペースを適用します
関数のパラメータが 0 の場合、vs2019 ではランダムな値になります
宇宙はプログラムなので、誰かが申請したら、最終的には誰が解放されるのでしょうか?
- プログラマが自分でリリースします。
- プログラムが終了し、オペレーティング システムがリサイクルされる
リリース方法は次のとおりです
2.無料
C 言語には、動的メモリの解放とリサイクルに特に使用される別の関数が無料で提供されており、関数のプロトタイプは次のとおりです。
- この関数は、malloc、calloc、realloc によって要求されたスペースを解放するために使用されます。
- パラメータ ptr が指す場合、スペースは動的に開かれませんの場合、free 関数の動作は未定義です。
- パラメータ ptr が NULL ポインタの場合、関数は何も行いません。
- この関数はポインタ ptr の値を変更しないため、ワイルド ポインタの出現を避けるために、適時に NULL に設定する必要があります。
3.calloc
この関数の機能は malloc に似ていますが、違いは次のとおりです。
- この関数には 2 つのパラメータがあり、1 つは要素の数、もう 1 つは各要素のサイズです。
- この関数は、要求されたスペースをすべて 0 に初期化します。
4.再割り当て
realloc 関数の登場により、動的メモリ管理がより柔軟になりました。
以前に申請したスペースが小さすぎることが判明する場合もあれば、申請したスペースが大きすぎると感じる場合もあります。メモリを合理的に使用するには、次のことを行う必要があります。メモリサイズの調整も柔軟に対応。次に、realloc 関数を使用して、動的に割り当てられたメモリ サイズを調整できます。
- ptr は調整するメモリアドレスです
- サイズ調整後の新しいサイズ
- 戻り値は調整後のメモリの開始位置です。
- この関数は、元のメモリ空間のサイズと
将原来内存中的数据移动到新的空间
を調整します。 - realloc がメモリ空間を調整する場合は 2 つの状況があります。
- 状況 1: 元のスペースの後ろに十分なスペースがある
- 状況 2: 元のスペースの後に十分なスペースがない
- ケース 1
ケース 1 の場合、メモリを拡張したい場合は、元のメモリの直後にスペースを追加するだけです。元の空間のデータは変更されません。- ケース 2
ケース 2 で、元のスペースの後に十分なスペースがない場合の拡張方法は次のとおりです。ヒープ スペース上で別の適切なスペースを見つけます。大小の連続したスペースを使用します。スペース。このようにして、関数は新しいメモリ アドレスを返します。
この関数には 2 つの特殊なケースがあるため、使用する場合はさらに注意する必要があります。
次のコードに問題はありますか?
int main()
{
//1. 先申请一部分空间
int* p = (int*)malloc(20);
if (NULL == p)
{
perror("malloc");
return 1;
}
//2.扩容空间
p = (int*)realloc(p, 40);
if (NULL == p)
{
perror("realloc");
return 1;
}
//3.使用
//.......
free(p);
p = NULL;
return 0;
}
realloc が領域を開くことができなかった場合、NULL ポインタが返されます。p は NULL ポインタです。
いいですね、拡張は成功しませんでした。以前のデータは消えてしまいました。それは素晴らしいですね。
したがって、realloc の戻り値を受け取る場合は、一時変数を使用してそれを受け取り、一時変数が null ポインタであるかどうかを判断して、p を操作する必要があります。
したがって、次のように記述する必要があります。
#include<stdlib.h>
int main()
{
//1. 先申请一部分空间
int* p = (int*)malloc(20);
if (NULL == p)
{
perror("malloc");
return 1;
}
//2.扩容空间
int* tmp = NULL;
tmp = (int*)realloc(p, 40);
//先判断realloc的返回值
if (tmp != NULL)
{
p = tmp;
tmp = NULL;
}
//3.使用
//....
free(p);
p = NULL;
return 0;
}
5. 動的メモリの一般的なエラー
- NULL ポインタの逆参照操作
- 動的に開かれた空間への境界外アクセス
- 非動的に割り当てられたメモリには自由解放を使用する
- free を使用して、動的に割り当てられたメモリの一部を解放します。
- 同じ動的メモリを複数回解放する
- 動的に割り当てられたメモリが解放され忘れられる (メモリ リーク)
6. 動的記憶に関する古典的な筆記試験問題の分析
- 質問1
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
これは値の転送であり、実際に str 用のスペースを開くわけではありません。str は NULL のままです。
- 質問2
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
p 変数のライフサイクルは GetMemory 関数内にあり、関数は終了し、変数は破棄されます。破壊された変数のアドレスを使用すると、予期しない結果が発生する
3. 質問 3
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
str が指す空間が解放され、str はワイルド ポインタですが、str は依然としてそのメモリ空間を指しており、ワールドを str にコピーします。不正アクセスされた。
7. 柔軟なアレイ
- フレキシブル配列とは
フレキシブル アレイの概念について聞いたことがないかもしれませんが、それは存在します。 C99では、構造的に の最后⼀个元素
は、サイズが不明な配列にすることができます。これは、「フレキシブル配列」メンバーと呼ばれます。
struct s
{
int i;
char ch;
int a[]; //int a[0]
};
ここで、 a は柔軟な配列です。
- フレキシブルアレイの特徴
- 構造体のフレキシブル配列メンバーの前少なくとも 1 つ必要です他のメンバー。
- sizeof によって返されるこの構造体のサイズフレックスアレイを除くメモリ。
- 柔軟な配列メンバーを含む構造体はmalloc () 関数を使用して動的にメモリを割り当てます。割り当てられたメモリは構造物のサイズよりも大きい、フレックス配列の予想されるサイズに対応するため。
- 柔軟なアレイの使用
柔軟な配列を使用しない場合、上記のコードをどのように実装すればよいでしょうか?
この目的はフレキシブル アレイを使用しなくても達成できますが、フレキシブル アレイを使用すると次の 2 つの利点があります。
- 便利なメモリ解放
私たちのコードが他の人が使用する関数内にある場合は、2 つのメモリ割り当てを行い、構造全体をユーザーに返します。ユーザーは free を呼び出すことで構造を解放できますが、ユーザーは構造内のメンバーも解放する必要があることを知らないため、ユーザーがこれを発見することは期待できません。したがって、構造体のメモリとそのメンバが必要とするメモリを一度に割り当て、構造体ポインタをユーザに返すと、ユーザは一度にすべてのメモリを解放することができます。
- アクセス速度に関しては良好
連続メモリは、アクセス速度の向上とメモリの断片化の軽減に役立ちます。
8. C/C++におけるメモリ領域の分割
- スタック領域
スタック領域(スタック):関数実行時に、関数内のローカル変数の記憶部をスタック上に作成でき、関数終了時にこの記憶部は自動的に解放されます。スタック メモリ割り当て操作はプロセッサの命令セットに組み込まれており、非常に効率的ですが、割り当てられるメモリ容量には制限があります。スタック領域には主に、関数を実行するために割り当てられたローカル変数、関数パラメータ、戻りデータ、戻りアドレスなどが格納されます。。
- ヒープ領域
ヒープ領域(ヒープ):通常はプログラマによって確保、解放されますが、プログラマが解放しない場合、プログラム終了時にOSによって再利用される場合があります。割り当て方法はリンク リストに似ています。
- 静的領域
データセグメント(静的領域):グローバル変数および静的に変更された変数を格納します。プログラム終了後にシステムにより解放されます。
- コードスニペット
コード部分:関数本体(クラスメンバ関数、グローバル関数)を格納します。バイナリコード。
今回の共有はこれで終わりです!間違いがあれば修正してください。