1. ダイナミックメモリー機能
動的メモリ割り当てはなぜ存在するのでしょうか?
int main(){
int num = 10; //向栈空间申请4个字节
int arr[10]; //向栈空间申请了40个字节
return 0;
}
上記のスペースを開く方法には 2 つの特徴があります。
- スペース割り当てサイズは固定です。
- 配列を宣言するときは、配列の長さを指定する必要があり、必要なメモリはコンパイル時に割り当てられます。
しかし、スペースの需要は上記の状況だけではありません。必要なスペースのサイズは、プログラムの実行中にのみ知ることができ、配列のコンパイル時にスペースを空ける方法が満足できない場合があります。このとき、動的メモリ開発が必要です。
1.1 malloc と無料
malloc()
プログラムの実行中にメモリを動的に割り当てるために使用されます。正式名称は「メモリアロケーション」で、メモリの割り当てを意味します。malloc()
関数は C 標準ライブラリの一部であり、その宣言はstdlib.h
ヘッダー ファイルにあります。
関数のプロトタイプは次のとおりです。
void* malloc(size_t size);
ここで、size
は割り当てたいバイト数であり、関数は割り当てられたメモリ ブロックの開始アドレスへのポインタを返します。malloc()
関数の戻り値の型は ですvoid*
。これは、返されたポインターを明示的な変換なしで任意のポインター型に割り当てることができることを意味します。
これがどのように機能するかを簡単に説明しますmalloc()
。
1. 割り当てるバイト数を指定し、
malloc()
それらのバイトを格納するのに十分な大きさの連続したメモリ ブロックをヒープ内で検索します。2. 適切なメモリ ブロックが見つかった場合は、そのメモリ ブロックを使用済みとしてマークし、メモリ ブロックの開始アドレスへのポインタを返します。
NULL
3. 十分な大きさのメモリ ブロックが見つからない場合は、メモリ割り当てが失敗したことを示すポインタを返します。
注:malloc()
割り当てられたメモリの使用はfree()
関数を使用して明示的に解放する必要があります。そうしないとメモリ リークが発生します。
void free(void* ptr);
free()
この関数は、以前に割り当てられたメモリ ブロックへのポインタを取得し、それを解放して、将来の動的割り当てに使用できるようにします。以前に割り当てられたメモリを解放するのを忘れた場合、プログラムは割り当てコードを実行するたびにより多くのメモリを消費し、最終的にはメモリが不足する可能性があります。
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int* dynamicArray = (int*)malloc(n * sizeof(int));
if (dynamicArray == NULL) {
printf("内存分配失败!\n");
} else {
// 使用分配的内存块
for (int i = 0; i < n; i++) {
dynamicArray[i] = i + 1;
}
// 当不再需要分配的内存时,记得释放它
free(dynamicArray);
dynamicArray = NULL;
}
return 0;
}
free()
関数を呼び出した後、動的に割り当てられたメモリを解放するためにポインタをdynamicArray
設定することをNULL
お勧めしますが、必須ではありません。
ポインタを に設定する利点NULL
:
- ダングリング ポインタを避ける (ダングリング ポインタ): メモリを解放した後にポインタが に設定されていない場合
NULL
、ポインタは以前のアドレスを保持したままになります。後続のコードでこのポインターを使用し続けると、ダングリング ポインターが発生する可能性があります。つまり、ポインターが指すメモリが解放され、プログラムがクラッシュしたり、デバッグが困難なエラーが発生したりする可能性があります。ポインターを に設定すると、NULL
null ポインターを使用しようとするとプログラムで明示的なエラー (null ポインター逆参照) が生成されるため、これを回避できます。- 二重解放を避ける: メモリを解放した後、ポインタを に設定すると
NULL
、NULL
ポインタが であるかどうかを確認することでメモリが解放されたかどうかを判断できます。後続のコードで誤って再度呼び出すとfree()
、未定義の動作が発生します。
ポインターがぶら下がったり、後続のコードでメモリを繰り返し解放したりしないように注意していれば、 に設定しなくてもNULL
問題は発生しません。ただし、これはバグに対するシンプルで役立つ追加の保護手段であるため、メモリを解放した後にポインタを に設定することをお勧めしますNULL
。
1.2 コールロック
calloc()
これも、標準 C ライブラリ (stdlib.h ヘッダー ファイル) に属する別の動的メモリ割り当て関数です。機能は似ていますmalloc()
が、使用方法にいくつかの違いがあります。
calloc()
関数のプロトタイプは次のとおりです。
void* calloc (size_t num, size_t size);
ここで、num
は割り当てる要素の数、 はsize
各要素のバイト単位のサイズです。この関数は、バイトのメモリ ブロックcalloc()
にスペースを割り当て、そのメモリ ブロック内のすべてのビットをゼロに初期化します。num * size
と比べた利点の 1 つはmalloc()
、calloc()
割り当てられたメモリが自動的に初期化されることです。つまり、割り当てられたメモリを手動でゼロにする必要がありません。場合によっては、これは、特に割り当てられたメモリが最初からゼロであることを確認する必要がある場合に非常に役立ちます。
例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int* dynamicArray = (int*)calloc(n, sizeof(int));
if (dynamicArray == NULL) {
printf("内存分配失败!\n");
} else {
// 使用分配的内存块,这里的内存已经被初始化为零
for (int i = 0; i < n; i++) {
printf("%d ", dynamicArray[i]); // 输出: 0 0 0 0 0
}
// 当不再需要分配的内存时,记得释放它
free(dynamicArray);
}
return 0;
}
要約:
calloc = malloc+memset は 0 に初期化されます
1.3 再割り当て
realloc
メモリブロックのサイズを再割り当てするために使用される関数です。malloc
具体的には、以前に渡された、または割り当てられたメモリ ブロックのサイズを変更するために使用できますcalloc
。
realloc
関数の宣言は次のとおりです。
void *realloc(void *ptr, size_t size);
パラメータの説明:
ptr
: 以前に割り当てられたメモリ ブロックへのポインタ。ptr
NULL の場合、新しいメモリ ブロックを割り当てるのとrealloc
同じ動作になります。malloc
size
: 新しいメモリ ブロック サイズ (バイト単位)。
realloc
このように動作します:
- NULLの場合
ptr
、realloc
動作はsize バイトのmalloc(size)
新しいメモリ ブロックを割り当てsize
、そのメモリ ブロックへのポインタを返すのと同じです。 size
これが 0ptr
であり、NULL ではない場合、その動作は、以前に割り当てられたメモリ ブロックを解放し、NULL ポインタを返すのとrealloc
同じです。free(ptr)
ptr
NULLsize
または場合は、realloc
以前に割り当てられたメモリ ブロックの再割り当てが試行されます。いくつかのことが起こる可能性があります。- 以前に割り当てられたメモリ ブロックのサイズがそれ以上の場合
size
、新しいメモリ ブロックは割り当てられませんが、元のメモリ ブロックの内容は変更せずに、元のメモリ ブロックへのポインタが返されるだけです。 - 以前に割り当てられたメモリ ブロックのサイズがそれより小さい場合は
size
、realloc
元のメモリ ブロックを新しいサイズに拡張しようとします。これは、**元のメモリ ブロックの背後にある利用可能なメモリ領域に拡張される可能性があり、拡張するのに十分な連続領域がない場合は、別の場所に新しいrealloc
メモリ ブロックを再割り当てし、元のコンテンツを新しいメモリ ブロックにコピーすることがあります。**これは、realloc
元のポインタの代わりに新しいポインタを返すことができることを意味するため、それを使用した後はrealloc
、返されたポインタを元のポインタに割り当てることをお勧めします。 - 新しいメモリ ブロックの割り当てに失敗した場合は
realloc
、NULL が返され、以前に割り当てられたメモリ ブロックは変更されません。
- 以前に割り当てられたメモリ ブロックのサイズがそれ以上の場合
を使用する場合はrealloc
、次の点に特別な注意を払う必要があります。
- NULL が返された場合は
realloc
、再割り当てが失敗し、元のポインタがまだ有効であることを意味します。メモリ リークを避けるために、元のポインタを保存し、必要に応じて前のメモリ ブロックを解放する必要があります。 - 使用する場合、予期しないメモリの問題を防ぐために、
realloc
生のポインターを直接変更せず、元のポインターの結果を生のポインターに割り当てることが最善です。realloc
例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(40);
if (p == NULL)
return 1;
//使用
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = i;
}
for (i = 0; i < 10; i++) {
printf("%d ", *(p + i));
}
//增加空间
// p = (int *)realloc(p, 80); //如果开辟失败的话,p变成了空指针,不能这么写
int *ptr = (int *) realloc(p, 80);
if (ptr != NULL) {
p = ptr;
ptr = NULL;
}
//当realloc开辟失败的时候,返回的也是空指针
//使用
for (i = 10; i < 20; i++) {
*(p + i) = i;
}
for (i = 10; i < 20; i++) {
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
//输出结果:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
2. 一般的な動的メモリエラー
2.1 NULL ポインタの逆参照操作
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(20);
*p = 5; //错误,空指针解引用
//为了不对空指针解引用 需要进行判断
if (p == NULL) {
perror("malloc");
return 1;
}
else {
*p = 5;
}
free(p);
p = NULL;
return 0;
}
2.2 動的に割り当てられたスペースへの境界外アクセス
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(20);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 20; i++)//越界访问 20个字节 只能访问5个整型
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
2.3 free を使用して非動的メモリを解放する
#include <stdio.h>
#include <stdlib.h>
int main() {
int a = 10;
int* p = &a;
free(p);// ok?
return 0;
}
コンパイラはエラーを直接報告します
2.4 free を使用して動的に割り当てられたメモリの一部を解放します
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(40);
if (p = NULL)
return 1;
int i = 0;
for (i = 0; i < 5; i++) {
*p = i;
p++;
}
//释放
//在释放的时候,p指向的不再是动态内存空间的起始位置
free(p);// p不再指向动态内存的起始位置
p++;
return 0;
}
2.5 同じ動的メモリの複数のリリース
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(40);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 5; i++) {
*(p + i) = i;
}
//重复free
free(p);
p = NULL;//如果将p赋值为NULL 就可以在free,否则编译器会直接报错
free(p);
return 0;
}
2.6 メモリを動的に開いて解放し忘れる (メモリ リーク)
#include <stdio.h>
#include <stdlib.h>
int *get_memory() {
int *p = (int *) malloc(40);
return p;
}
int main() {
int *ptr = get_memory();
//使用
//释放 如果不释放 就会导致内存泄漏
free(ptr);
return 0;
}
3. C/C++プログラムのメモリ割り当て
C/C++ プログラムのメモリ割り当てのいくつかの領域:
- スタック領域(スタック):関数実行時にスタック上に関数内のローカル変数の記憶部を作成でき、関数の実行終了時に自動的に解放されます。スタック メモリ割り当て操作はプロセッサの命令セットに組み込まれており、非常に効率的ですが、割り当てられるメモリ容量には制限があります。スタック領域には主に、関数の実行により割り当てられるローカル変数、関数のパラメータ、戻りデータ、戻りアドレスなどが格納されます。
- ヒープ領域(ヒープ):通常、プログラマによって割り当ておよび解放されますが、プログラマが解放しない場合は、プログラムの終了時にOSによって回復される場合があります。割り当て方法はリンク リストに似ています。
- データセグメント(静的領域)(static)には、グローバル変数と静的データが格納されます。番組終了後にシステムにより解放されます。
- コードセグメント:関数本体(クラスメンバ関数およびグローバル関数)のバイナリコードを格納します。
通常のローカル変数はスタック領域に領域が確保されますが、スタック領域の特徴は、上で作成した変数がスコープ外に出ると破棄されることです。ただし、staticで変更した変数はデータセグメント(静的領域)に格納されるため、データセグメント上に作成した変数はプログラム終了まで破棄されないという特徴があり、ライフサイクルが長くなります。
4. 古典的な筆記試験の問題
4.1 トピック 1
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char *p) {
p = (char *) malloc(100);
}
void Test(void) {
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main() {
Test();
return 0;
}
テスト機能を実行すると結果はどうなりますか?
Test
関数を実行すると、未定義の動作が発生します。
GetMemory
関数内ではローカル変数が渡されますchar *p
が、関数内でローカル変数が変更されても、元の呼び出し関数のポインタには影響しません。これは、関数のパラメーターが値によって渡されるためです。つまり、関数は実際のパラメーターのコピーを取得し、パラメーターを変更しても元の実パラメーターには影響しません。
Test
関数内では、NULL ポインタが関数str
に渡され、関数内でメモリが割り当てられ、新しいアドレスが に割り当てられます。しかし、これはそれに影響を与えず、未割り当てメモリを指す NULL ポインタのままです。GetMemory
GetMemory
p
str
str
次に、Test
関数内で を使用して、指定されたメモリstrcpy
に文字列をコピーしますが、指定されたメモリが割り当てられていないため、未定義の動作が発生します。str
str
メモリを正しく割り当ててポインタを使用するには、割り当てられたメモリ アドレスを返し、返されたポインタを関数内で受け取るGetMemory
ように関数を変更する必要があります。Test
また、使用後にfree
動的に割り当てられたメモリを解放するには関数を使用する必要があることを忘れないでください。
書き換え 1:
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char **p) {
*p = (char *) malloc(100);
}
void Test(void) {
char *str = NULL;
GetMemory(&str); //传指针的地址
strcpy(str, "hello world");
printf(str);
//释放
free(str);
str = NULL;
}
int main() {
Test();
return 0;
}
書き換え 2:
#include <stdio.h>
#include <stdlib.h>
char *GetMemory() {
char *p = (char *) malloc(100);
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory(); //接受返回的p
strcpy(str, "hello world");
printf(str);
//释放
free(str);
str = NULL;
}
int main() {
Test();
return 0;
}
4.2 トピック 2
char *GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
printf(str);
}
テスト機能を実行すると結果はどうなりますか?
GetMemory
関数では、ローカル配列が定義されchar p[] = "hello world";
、配列のアドレスが呼び出し元に返されます。ただし、GetMemory
関数の実行が終了すると、そのローカル変数 (p
配列) は自動ストレージ クラスのローカル変数であるため、破棄されます。したがって、返されたポインタは、有効ではなくなったメモリを指します。
Test
関数では、GetMemory
の戻り値をポインター に代入しstr
、を使用して指す内容printf
を出力しますstr
。GetMemory
無効なポインタ (破棄されたローカル配列を指す) が返されるため、printf
ガベージ値が出力されたり、プログラムがクラッシュしたり、その他の予期しない結果が発生したりする可能性があります。
この問題は、ポインタが有効ではなくなったメモリ位置を指してぶら下がっているため、「ダングリング ポインタ」問題として知られています。
この問題を解決するには、動的メモリ割り当てを使用して文字列を格納するためのメモリを割り当て、 use の後に必ず を使用してメモリを解放することを検討できますfree
。
変更されたコード例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *GetMemory(void) {
char *p = (char *)malloc(strlen("hello world") + 1);
if (p != NULL) {
strcpy(p, "hello world");
}
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
if (str != NULL) {
printf("%s\n", str);
free(str); // 释放内存
}
}
int main() {
Test();
return 0;
}
4.3 トピック 3
void GetMemory(char **p, int num) {
*p = (char *) malloc(num);
}
void Test(void) {
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
テスト機能を実行すると結果はどうなりますか?
メモリが解放されず、メモリ リークが発生する
変更されたコード例:
void GetMemory(char **p, int num) {
*p = (char *)malloc(num);
}
void Test(void) {
char *str = NULL;
GetMemory(&str, 100);
if (str != NULL) {
strcpy(str, "hello");
printf("%s\n", str);
free(str); // 释放内存
}
}
4.4 トピック 4
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL) {
strcpy(str, "world");
printf(str);
}
}
テスト機能を実行すると結果はどうなりますか?
str が早期に解放されると、str に再度アクセスするとポインタの動作が不安定になります。