C、C++、Javaの観点から、誰でも明確に説明できますか。すべてがスタックになり、すべてがヒープに行き、いつ割り当てが行われるか。
私の知る限りでは、
関数呼び出しごとのプリミティブ、ポインター、または参照変数に関係なく、すべてのローカル変数は新しいスタックフレーム上にあります。
そして、newまたはmallocで作成されたものはすべてヒープになります。
私はいくつかのことについて混乱しています。
ヒープ上に作成されたオブジェクトのメンバーである参照/プリミティブもヒープに保存されますか?
そして、各フレームで再帰的に作成されているメソッドのローカルメンバーについてはどうでしょうか。それらはすべてスタック上にありますか?はいの場合、そのスタックメモリは実行時に割り当てられますか?リテラルの場合も、それらはコードセグメントの一部ですか? Cのグローバル、C++/Javaの静的、Cの静的についてはどうでしょうか。
メモリ内のプログラムの構造
以下は、メモリにロードされたときのプログラムの基本構造です。
+--------------------------+
| |
| command line |
| arguments |
| (argc and argv[]) |
| |
+--------------------------+
| Stack |
| (grows-downwards) |
| |
| |
| |
| F R E E |
| S P A C E |
| |
| |
| |
| |
| (grows upwards) Heap |
+--------------------------+
| |
| Initialized data |
| segment |
| |
+--------------------------+
| |
| Initialized to |
| Zero (BSS) |
| |
+--------------------------+
| |
| Program Code |
| |
+--------------------------+
注意すべきいくつかのポイント:
データセグメント
データセグメントには、初期化された値を含むユーザーによって明示的に初期化されるグローバルデータと静的データが含まれます。
データセグメントの他の部分はBSSと呼ばれます(古いIBMシステムではそのセグメントがゼロに初期化されていたため)。これは、OSがメモリブロックをゼロに初期化するメモリの部分です。これにより、初期化されていないグローバルデータと静的データがデフォルト値としてゼロになります。この領域は固定されており、静的なサイズです。
データ領域は、初期化される変数を1つずつ初期化できるため、明示的な初期化に基づいて2つの領域に分けられます。ただし、初期化されていない変数は、0を1つずつ明示的に初期化する必要はありません。その代わりに、変数を初期化する作業はOSに任されています。この一括初期化により、実行可能ファイルのロードに必要な時間を大幅に短縮できます。
ほとんどの場合、データセグメントのレイアウトは、基盤となるOSの制御下にありますが、一部のローダーはユーザーに部分的な制御を提供します。この情報は、組み込みシステムなどのアプリケーションで役立つ場合があります。
この領域は、コードからのポインターを使用してアドレス指定およびアクセスできます。自動変数には、必要になるたびに変数を初期化するオーバーヘッドがあり、その初期化を行うにはコードが必要です。ただし、初期化は1回だけ実行され、ロード時にも実行されるため、データ領域の変数にはそのような実行時の過負荷はありません。
コードセグメント
プログラムコードは、実行可能コードを実行できるコード領域です。この領域も固定サイズです。これは関数ポインタからのみアクセスでき、他のデータポインタからはアクセスできません。ここで注意すべきもう1つの重要な情報は、システムがこの領域を読み取り専用メモリ領域と見なす可能性があり、この領域に書き込もうとすると、未定義の動作が発生することです。
定数文字列は、コード領域またはデータ領域のいずれかに配置できます。これは、実装によって異なります。
コード領域に書き込もうとすると、未定義の動作が発生します。たとえば(C
ベースの例のみを示します)、次のコードはランタイムエラーを引き起こしたり、システムをクラッシュさせたりする可能性があります。
int main()
{
static int i;
strcpy((char *)main,"something");
printf("%s",main);
if(i++==0)
main();
}
スタックおよびヒープ領域
プログラムは、実行のために、スタックとヒープの2つの主要部分を使用します。スタックフレームは、関数のスタックと動的メモリ割り当てのヒープに作成されます。スタックとヒープは初期化されていない領域です。したがって、メモリ内にあるものはすべて、そのスペースで作成されたオブジェクトの初期(ガベージ)値になります。
サンプルプログラムを見て、どの変数がどこに格納されるかを示しましょう。
int initToZero1;
static float initToZero2;
FILE * initToZero3;
// all are stored in initialized to zero segment(BSS)
double intitialized1 = 20.0;
// stored in initialized data segment
int main()
{
size_t (*fp)(const char *) = strlen;
// fp is an auto variable that is allocated in stack
// but it points to code area where code of strlen() is stored
char *dynamic = (char *)malloc(100);
// dynamic memory allocation, done in heap
int stringLength;
// this is an auto variable that is allocated in stack
static int initToZero4;
// stored in BSS
static int initialized2 = 10;
// stored in initialized data segment
strcpy(dynamic,”something”);
// function call, uses stack
stringLength = fp(dynamic);
// again a function call
}
または、さらに複雑な例を考えてみましょう。
// command line arguments may be stored in a separate area
int main(int numOfArgs, char *arguments[])
{
static int i;
// stored in BSS
int (*fp)(int,char **) = main;
// points to code segment
static char *str[] = {"thisFileName","arg1", "arg2",0};
// stored in initialized data segment
while(*arguments)
printf("\n %s",*arguments++);
if(!i++)
fp(3,str);
}
お役に立てれば!
C/C++の場合:ローカル変数は、現在のスタックフレーム(現在の関数に属する)に割り当てられます。オブジェクトを静的に割り当てる場合、すべてのメンバー変数を含むオブジェクト全体がスタックに割り当てられます。再帰を使用する場合、関数呼び出しごとに新しいスタックフレームが作成され、すべてのローカル変数がスタックに割り当てられます。スタックのサイズは通常固定されており、この値は通常、コンパイル/リンク中に実行可能バイナリヘッダーに書き込まれます。ただし、これは非常にOSとプラットフォームに固有であり、一部のOSは必要に応じてスタックを動的に拡張する場合があります。通常、スタックのサイズは制限されているため、深い再帰を使用する場合、または大きなオブジェクトを静的に割り当てるときに再帰がない場合でも、スタックが不足する可能性があります。
ヒープは通常、無制限のスペース(使用可能な物理/仮想メモリによってのみ制限されます)と見なされ、malloc/new(およびその他のヒープ割り当て関数)を使用してヒープにオブジェクトを割り当てることができます。オブジェクトがヒープ上に作成されると、そのすべてのメンバー変数がその中に作成されます。オブジェクトは、どこに割り当てられていても、メモリの連続領域(この領域にはメンバー変数と仮想メソッドテーブルへのポインタが含まれます)として表示されます。
リテラル、定数、およびその他の「固定」のものは通常、別のセグメントとしてバイナリにコンパイル/リンクされるため、実際にはコードセグメントではありません。通常、実行時にこのセグメントから何かを割り当てたり解放したりすることはできません。ただし、これもプラットフォーム固有であり、プラットフォームによって動作が異なる場合があります(たとえば、iOS Obj-Cコードには、関数間でコードセグメントに直接挿入された多くの定数参照があります)。
CおよびC++では、少なくとも、これはall実装固有です。標準では、「スタック」または「ヒープ」については言及されていません。
Javaでは、ローカル変数をスタックに割り当てることができます(最適化されていない限り)
オブジェクト内のプリミティブと参照はヒープ上にあります(オブジェクトがヒープ上にあるため)
スタックは、スレッドの作成時に事前に割り当てられます。ヒープスペースを使用しません。 (ただし、スレッドを作成すると、スレッドローカル割り当てバッファーが作成され、空きメモリが大幅に減少します)
一意の文字列リテラルがヒープに追加されます。プリミティブリテラルは、コードのどこかにある可能性があります(最適化されていない場合)。フィールドが静的であるかどうかに関係なく、違いはありません。
セクション3.5 の Java仮想マシン仕様 ランタイムデータ領域(スタックとヒープ)について説明します。
C言語標準もC++言語標準も、何かをスタックまたはヒープのどちらに格納するかを指定していません。これらは、オブジェクトの存続期間、可視性、および変更可能性のみを定義します。これらの要件を特定のプラットフォームのメモリレイアウトにマッピングするのは、実装次第です。
通常、*alloc
関数で割り当てられたものはすべてヒープに存在し、auto
変数と関数パラメーターはスタックに存在します。文字列リテラルは「どこか別の場所」に存在する可能性があります(プログラムの存続期間中に割り当てて表示する必要がありますが、変更を試みることは定義されていません)。一部のプラットフォームは、個別の読み取り専用メモリセグメントを使用してそれらを格納します。
一般的なスタックヒープモデルに準拠していない可能性のある、本当に奇妙なプラットフォームがいくつかあることを覚えておいてください。
C++ヒープとスタックに関する質問の一部に答えるには:
Newなしで作成されたオブジェクトは、スタック上の連続したユニットとして、またはグローバルセグメント(プラットフォーム固有)でグローバルに格納されることを最初に言う必要があります。
New on the heapを使用して作成されたオブジェクトの場合、そのメンバー変数は、ヒープ上の1つの連続したメモリブロックとして格納されます。これは、プリミティブおよび埋め込みオブジェクトであるメンバー変数の場合です。ポインターおよび参照型メンバー変数であるメンバー変数の場合、プリミティブポインター値はオブジェクト内に格納されます。その値が指すものは、どこにでも(ヒープ、スタック、グローバル)格納できます。何でも可能です。
オブジェクトのメソッド内のローカル変数については、ヒープ上のオブジェクトの連続したスペース内ではなく、スタックに格納されます。スタックは通常、実行時に固定サイズで作成されます。スレッドごとに1つあります。ローカル変数は、スタックから最適化される可能性があるため、スタック上のスペースを消費することさえありません(Paulが述べたように)。重要な点は、それらがヒープ上のオブジェクトのメンバー関数であるという理由だけで、ヒープ上にないということです。それらがポインタ型のローカル変数である場合、それらはスタックに格納され、ヒープまたはスタック上の何かを指す可能性があります。