最近、ブロガーは C++ での動的メモリ割り当てについて学びました。学習内容には、動的メモリ割り当てが必要な理由、動的メモリ割り当ての実行方法、C++ メモリのメカニズム (ヒープとスタックの違い)、メモリ オーバーフロー/リーク、ワイルド ポインタ/ダングリング ポインタ、結合されたコード例の分析などが含まれます。
同時に、優れた記事も多数お借りしておりますが、中には参考にならないものもございますので、もし権利侵害があった場合には削除のご連絡をさせていただきます。
1. 動的メモリ割り当て
1. 動的メモリ割り当てが必要なのはなぜですか?
場合によっては、プログラムが必要とするメモリの量は事前に予測できず、プログラムの実行中にしか分からないことがあります。この場合、すべての変数を事前に定義することはできませんが、プログラムの実行時に人為的かつ動的にメモリを割り当てる必要があります。
2. 動的メモリ割り当てを実行するにはどうすればよいですか?
新規/削除演算子。
(1) 単純な変数の動的メモリ割り当て
#include <iostream>
#include <windows.h>
using namespace std;
int main ()
{
int *p1 = new int(1);
float *p2 = new float(2.0f);
char *p3 = new char('c');
/*
或者上述代码也可以写成:
int *p1 = new int;
*p1 = 1;
*/
cout<<*p1<<endl;
cout<<*p2<<endl;
cout<<*p3<<endl;
cout<<p1<<endl;
cout<<p2<<endl;
cout<<p3<<endl;
delete p1;
delete p2;
delete p3;
cout<<"\n"<<endl;
system("pause");
return 0;
}
実行結果:
ただし、コンピュータのスペースには限りがあります。スペースが使い果たされ、メモリを割り当てることができない場合は、どうすればよいですか? 次のコードを使用して、メモリ割り当てが成功したかどうかを確認できます。
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
(2) 配列の動的メモリ割り当て
#include <iostream>
#include <windows.h>
using namespace std;
int main ()
{
int* array = new int[5]; //分配内存
for(int i = 0 ; i < 5 ; i++){
array[i] = i;
cout<<*(array+i)<<endl; //这里也可以写成array[i]
}
delete [] array; //删除内存
cout<<"\n"<<endl;
system("pause");
return 0;
}
操作結果:
(3) オブジェクトの動的メモリ割り当て(省略)
2、C++ メモリ メカニズム
1. スタックエリア
メインストレージ: 関数パラメータ、ローカル変数。
メモリを割り当てる場合は、スタック領域の上位アドレスから下位アドレスに向かって空きメモリを見つけて割り当てます。
関数内で宣言されたすべての変数はスタック メモリを使用します。调用函数时,分配内存,函数结束时,内存自动释放。每调用一个函数就会给它分配一段连续的栈空间。
2. ヒープ領域
メインストレージ: プログラマは new メモリと malloc メモリを使用します。
メモリを割り当てる場合は、メモリのヒープ領域の下位アドレスから上位アドレスまで空きメモリ領域を探して割り当てます。
メモリを解放するには、delete、free を使用します。
3. グローバルゾーン/スタティックゾーン
メインストレージ: プログラム内のグローバル変数、静的静的変数。
メモリを割り当てる場合は、グローバル領域の下位アドレスから上位アドレスまで空きメモリ領域を見つけて割り当ててください。
4. 一定面積
文字列定数と整数定数を格納します。
5. コードエリア
プログラムによって実行される CPU 命令を保存します。
補足1:スタック領域とヒープ領域の利用特性を深く理解する
1. #include <stdio.h>
2. #include <stdlib.h>
3.
4. int main() {
5. int a;
6. int *p;
7. p = (int *)malloc(sizeof(int));
8. free(p);
9.
10. return 0;
11. }
上記のコードは非常に単純です。変数 a と p の 2 つがあり、その型はそれぞれ int と int * です。このうち、a と p はスタック上に格納され、p の値はヒープ上のアドレスです (上記のコード、p の値は 0x1c66010)、上記のコードのレイアウトを次の図に示します。
補足2: ヒープとスタックの違い
3. メモリ リーク、メモリ オーバーフロー、ワイルド ポインタ、ダングリング ポインタ
メモリーリーク
メモリ リーク: メモリの特定の部分が解放される必要がありますが、さまざまな理由により解放されず、メモリが無駄に消費されます。
メモリ リークのケース:
エラーの原因: malloc および new によって要求されたメモリがアクティブに解放されていません。
#include <iostream>
#include <windows.h>
using namespace std;
int main ()
{
while(true){
int *s = (int*)malloc(50);
}
cout<<"\n"<<endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int *a = new int(123);
cout << *a << endl;
// We should write "delete a;" here
a = new int(456);
cout << *a << endl;
delete a;
return 0;
}
注:
(1) 関数内で定義された一時変数ポインタは、他の変数と同様に、関数終了時に自動的に解放されますが、新しいメモリ空間は解放されません通过new分配的内存一定要手动释放
。
(2) ポインタが定義されているが値が割り当てられていない場合、ポインタは NULL になります。对空指针应用delete是安全的
。
メモリ不足
メモリ オーバーフロー: システムは必要なスペースを割り当てることができなくなりました。たとえば、現在システムには 1G のスペースしかありませんが、2G のスペースが必要な場合、これはメモリ オーバーフローと呼ばれます。
メモリ リークは最終的にメモリ オーバーフローを引き起こします。
ワイルドポインター
簡単に言えば、初期化されていないポインタ
簡単に言えば、使用できないメモリ領域へのポインタを指します。ワイルドポインタはゴミメモリ領域へのポインタを指しており、一度使用すると予期せぬ結果を引き起こすことが多く、このようなランダムで予期せぬ結果が最も恐ろしいものです。
例えば:
void func() {
int* p;
cout<<*p<<endl;
}
その結果、p は乱数を出力します。
ぶら下がりポインタ:
簡単に言うと、ポインタ オブジェクトが削除されると、ダングリング ポインタになります。
ダングリング ポインタも一般的なタイプのワイルド ポインタです。メモリからオブジェクトを明示的に削除するか、スタック フレームを破棄して戻る場合、関連付けられたポインタの値は変更されません。このポインタは実際には依然としてメモリ内の同じ位置を指しており、その位置でさえ読み取りと書き込みは可能ですが、このメモリが他のプログラムまたはコードによって使用されているかどうかを保証できないため、現時点ではメモリ領域は完全に制御できません。 。
例えば:
void dangling_point() {
char *p = (char *)malloc(10);
strcpy(p, "abc");
cout<<p<<endl;
free(p);
if (p != NULL) cout<<p<<endl;
strcpy(p, "def");
}
free 文を実行すると、p が指すメモリが解放され、その時点でメモリ領域は使用できないメモリになります。ただし、このとき p が指すアドレスは変化しておらず、空にはならないため、(p != NULL) が true と判定された場合は、以降の文が実行され続けます。
ただし、 strcpy(p, "def"); コードはエラーやクラッシュを報告しませんが、この時点で動的メモリ領域を改ざんしているため、予測できない結果が生じます。この操作は、特にデバッグの場合、非常に危険です。トラブルシューティングは非常に困難です。
解決策: ダングリング ポインターの問題を回避するために、ポインターの解放/削除の後に p=NULL を設定するのが一般的です。
4. 実際の事例
次の 2 つのコードを見て、出力の違いを考えてください。
一:
#include <iostream>
#include <windows.h>
using namespace std;
class People{
public:
string name;
long birthday;
};
People* getPeople(){
People px;
px.birthday = 1111;
return &px;
/*
返回&px程序会出现警告:
address of local variable 'px' returned [-Wreturn-local-addr]gcc
原因:函数内部定义的变量在函数结束时会被释放掉,所以返回一个地址是不行的。
*/
}
int main ()
{
People *p = getPeople();
cout<<p->birthday<<endl;
system("pause");
return 0;
}
二:
#include <iostream>
#include <windows.h>
using namespace std;
class People{
public:
string name;
long birthday;
};
People getPeople(){
People px;
px.birthday = 1111;
return px;
}
int main (){
People p = getPeople();
cout<<p.birthday<<endl;
system("pause");
return 0;
}
一見したところ、両者に違いはありませんが、最初のプログラムで次の警告が表示されます: ローカル変数 'px' のアドレスが返されました [-Wreturn-local-addr] gcc 理由: コードは 1 つで、リターン
ははアドレスであり、コード 2 で返されるのは実際の値です。
カスタム関数のローカル変数はスタック領域に格納され、関数の実行開始時にメモリ空間が自動的に割り当てられ、関数の実行終了時にメモリ空間が自動的に解放されることがわかります。
現時点では、関数内で内部変数のアドレスを返すことはできません。関数の実行後、これらの一時メモリ空間が解放され、変数が消えるためです。アドレスは返しましたが、今後このアドレスで消えた記憶空間を探すのは寂しさしかありません。
このとき、関数内で変数の値を返すことが可能です。返されるのは実際のデータであるため、データを渡し、そこで値を受け取ります。
参考文献
1.警告: 関数がローカル変数のアドレスを返す【関数が最初のアドレスエラーを返す理由】
2. C++ワイルドポインタのまとめ
3. C/C++のメモリリークの原因と回避方法と場所
4. 【C++】C++の詳細解説動的メモリ割り当て (new/new [] および delete/delete[])