stackとheapメモリの違いを理解しようとしています。そして この質問 on SOと同様に この説明 は基本を説明するのにかなり良い仕事をしました。
ただし、2番目の説明では、特定の質問がある例に出会いました。例は次のとおりです。
オブジェクトm
はheapに割り当てられると説明されていますが、これが完全なストーリーかどうか疑問に思っています。私の理解によると、new
キーワードがインスタンス化に使用されているため、オブジェクト自体はheapに実際に割り当てられます。
ただし、オブジェクトm
へのポインタが、スタック上に同時に割り当てられているではありませんか?それ以外の場合、heapにあるオブジェクト自体はどのようにアクセスされますか。完全を期すために、これはこのチュートリアルで言及されるべきでしたが、それを残しておくと少し混乱を招きます。基本的に、次の2つのステートメントが必要です。
1。オブジェクトm
へのポインターがスタックに割り当てられました
2。オブジェクトm
自体(したがって、オブジェクトが運ぶデータ、およびそのメソッドへのアクセス)はヒープに割り当てられています
あなたの理解は正しいかもしれませんが、陳述は間違っています:
オブジェクト
m
へのポインターがスタックに割り当てられています。
m
はポインターです。スタック上にあります。おそらく、あなたはMember
オブジェクトへのポインタを意味していました。
オブジェクト
m
自体(オブジェクトが運ぶデータ、およびそのメソッドへのアクセス)は、ヒープに割り当てられています。
正しいのはm
が指すオブジェクトがヒープ上に作成されると言うことです
一般に、関数/メソッドのローカルオブジェクトと関数パラメーターはスタック上に作成されます。 m
は関数ローカルオブジェクトであるため、スタック上にありますが、m
が指すオブジェクトはヒープ上にあります。
「スタック」と「ヒープ」は、一般的なプログラミング用語です。特に、スタックまたはヒープデータ構造を介して内部的に管理されるストレージは必要ありません。
C++には次のストレージクラスがあります
おおよそ、動的は「ヒープ」に対応し、自動は「スタック」に対応します。
あなたの質問に移ります:これら4つのストレージクラスのいずれにもポインターを作成できます。また、ポイントされているオブジェクトは、これらのストレージクラスのいずれかに属します。いくつかの例:
void func()
{
int *p = new int; // automatic pointer to dynamic object
int q; // automatic object
int *r = &q; // automatic pointer to automatic object
static int *s = p; // static pointer to dynamic object
static int *s = r; // static pointer to automatic object (bad idea)
thread_local int **t = &s; // thread pointer to static object
}
指定子なしで宣言された名前付き変数は、関数内の場合は自動、そうでない場合はstaticです。
関数で変数を宣言すると、常にスタックに配置されます。したがって、変数Member* m
がスタック上に作成されます。 m
自体は単なるポインターであることに注意してください。何も指していない。これを使用して、スタックまたはヒープ上のオブジェクトを指すか、まったく何も指すことができません。
クラスまたは構造体の変数の宣言は異なります。クラスまたは構造体がインスタンス化される場所に移動します。
ヒープ上に何かを作成するには、new
またはstd::malloc
(またはそれらのバリアント)を使用します。この例では、new
を使用してヒープ上にオブジェクトを作成し、そのアドレスをm
に割り当てます。メモリリークを回避するには、ヒープ上のオブジェクトを解放する必要があります。 new
を使用して割り当てられた場合、delete
を使用する必要があります。 std::malloc
を使用して割り当てられた場合、std::free
を使用する必要があります。より良いアプローチは、通常、「スマートポインター」を使用することです。これは、ポインターを保持し、それを解放するデストラクタを持つオブジェクトです。
はい、ポインターはスタックに割り当てられますが、ポインターが指すオブジェクトはヒープに割り当てられます。正解です。
しかし、オブジェクトmへのポインターがスタック上で同時に割り当てられているのではないでしょうか?
Member
オブジェクトを意味すると思います。ポインタはスタック上に割り当てられ、関数(またはそのスコープ)の全期間にわたってそこに持続します。その後、コードはまだ機能する可能性があります。
#include <iostream>
using namespace std;
struct Object {
int somedata;
};
Object** globalPtrToPtr; // This is into another area called
// "data segment", could be heap or stack
void function() {
Object* pointerOnTheStack = new Object;
globalPtrToPtr = &pointerOnTheStack;
cout << "*globalPtrToPtr = " << *globalPtrToPtr << endl;
} // pointerOnTheStack is NO LONGER valid after the function exits
int main() {
// This can give an access violation,
// a different value after the pointer destruction
// or even the same value as before, randomly - Undefined Behavior
cout << "*globalPtrToPtr = " << *globalPtrToPtr << endl;
return 0;
}
上記のコードは、スタック上にあるポインターのアドレスを格納します(Object
のdelete
で割り当てられたメモリを解放しないため、メモリもリークします)。
関数を終了した後、ポインターは「破棄」されるため(つまり、そのメモリはプログラムを満足させるために使用できます)、安全にアクセスできなくなります 。
上記のプログラムは、適切に実行するか、クラッシュするか、別の結果を与えることができます。解放または解放されたメモリへのアクセスは、undefined behaviorと呼ばれます。