3

したがって、これは広く回答されている質問のように思えるかもしれませんが、私は、この 2 つの間で正確に何が違うのかという内部にもっと興味があります。

2 番目の例では、メモリだけでなくメモリへのポインタも作成されるという事実以外に、次の場合にメモリ内で何が起こるかを示します。

char a[5];
char b* = new char[5];

そして、なぜ私がこの質問をしたのか、どうして私はできるのかにもっと直接的に関連しています

const int len = 5;
char* c = new char[len];

だがしかし

const int len = 5;
char d[len]; // Compiler error

編集VC ++でこのコンパイラエラーが発生していることに言及する必要がありました(図を参照してください...)

1>.\input.cpp(138) : error C2057: expected constant expression
1>.\input.cpp(138) : error C2466: cannot allocate an array of constant size 0
1>.\input.cpp(138) : error C2133: 'd' : unknown size

EDIT 2 :私が作業していた正確なコードを投稿する必要がありました。このエラーは、動的に割り当てられた配列の定数の長さが実行時の値で計算されるときに発生します。

と の間をrandom(a,b)返すと仮定すると、intab

const int len1 = random(1,5);
char a[len1]; // Errors, since the value
              // is not known at compile time (thanks to answers)

一方

const int len2 = 5;
char b[len2]; // Compiles just fine
4

8 に答える 8

16

違いは、アレイの寿命です。あなたが書く場合:

char a[5];

次に、配列には、それが定義されているブロックの有効期間 (ブロック スコープで定義されている場合)、それを含むクラス オブジェクトの有効期間 (クラス スコープで定義されている場合)、または静的な有効期間 (名前空間スコープで定義されている場合) があります。あなたが書く場合:

char* b = new char[5];

の場合、配列には指定したい任意の有効期間があります。その有効期間を次のように明示的に終了する必要があります。

delete [] b;

そして、あなたの最後の質問に関して:

int const len = 5;
char d[len];

完全に合法であり、コンパイルする必要があります。違いがあるところ:

int len = 5;    //  _not_ const
char d[len];    //  illegal
char* e = new char[len];    //  legal

この違いの主な理由は、コンパイラのテクノロジと歴史の 1 つです。ごく初期の頃、コンパイラは配列をローカル変数として作成するために長さを知る必要がありました。

于 2012-06-27T13:04:01.993 に答える
6

次の場合にメモリ内で何が起こるか:

char a[5]; 
char *b = new char[5];

典型的ではあるがやや単純化された C++ 実装で、上記のコードが関数内にあると仮定します。

char a[5];

スタックポインタを 5 バイト移動し、5 バイトの空間を作ります。この名前aは、5 バイトのメモリのブロックを参照するようになりました。

char *b = new char[5];

スタックポインタは によって移動されsizeof(char*)、 のためのスペースが作られbます。関数が呼び出され、「フリー ストア」と呼ばれるものから 5 バイトが割り当てられます。基本的には、OS から取得された大きなメモリ ブロックから 5 バイト以上が切り出され、ブックキーピングが実行されます。でこれらのバイトを解放するとdelete[]、将来の割り当てで再利用できるようになります。割り当てられた 5 バイトのブロックのアドレスを返します。このブロックは、 のスタック上のスペースに格納されますb

2 番目の作業が最初の作業よりも多い理由は、割り当てられたオブジェクトnewが任意の順序で削除される可能性があるためです。ローカル変数 (「スタック上のオブジェクト」とも呼ばれる) は、作成された順序とは逆の順序で常に破棄されるため、簿記が少なくて済みます。自明に破壊可能な型の場合、実装はスタック ポインターを同じ距離だけ反対方向に移動できます。

私が行った単純化のいくつかを削除するには: スタック ポインターは実際には変数ごとに 1 回移動されません。関数内のすべての変数の関数エントリで 1 回だけ移動される可能性があります。この場合、必要なスペースは少なくともsizeof(char*) + 5. スタック ポインターまたは個々の変数にアラインメント要件がある場合があります。これは、必要なサイズではなく、切り上げられた量だけ移動することを意味します。実装 (通常はオプティマイザ) は、未使用の変数を削除するか、スタック領域の代わりにレジスタを使用できます。おそらく私が考えていない他のいくつかのこと。

const int len1 = random(1,5);

言語規則はかなり単純です。配列のサイズは定数式でなければなりません。変数が同じ TU に初期化子を持ち、その初期化子が定数式である場合const int、変数名を定数式で使用できます。random(1,5)は定数式ではないため、定数式ではlen1使用できません。5は定数式なのでlen2大丈夫です。

言語規則があるのは、コンパイル時に配列のサイズが確実にわかるようにするためです。stack_pointer -= 5したがって、スタックを移動するために、コンパイラは(where stack_pointerwill be esp、または、または何でも) と同等の命令を発行できますr13。それを行った後でも、すべての変数がスタックポインターの新しい値からどのようなオフセットを持っているかを正確に「認識」しています-古いスタックポインターとは5異なります。可変スタック割り当ては、実装により大きな負担をもたらします。

于 2012-06-27T13:12:04.033 に答える
2

次の場合にメモリ内で何が起こるか:

char a[5];
char b* = new char[5];

char a[5]スタック メモリに 5 文字を割り当てます。
new char[5]ヒープ メモリに 5 文字を割り当てます。


そして、私がこの質問をした理由にもっと直接的に関連しています。

const int len = 5;
char* c = new char[len];

だがしかし

const int len = 5;
char d[len]; // Compiler error

どちらも正常にコンパイルされています。

于 2012-06-27T13:03:49.810 に答える
1

C++ では、動的配列をスタックに入れることはできません。C99 にはこの機能がありますが、C++ にはありません。

宣言すると、 stackchar d[ len ]にスペースが割り当てられます。その際、 heapにスペースを割り当てます。char *c = new char[ len ]

ヒープにはマネージャーがあり、可変量のメモリを割り当てることができます。C++ では、定数式の値によってスタックを割り当てる必要があるため、コンパイラには多くの最適化の余地があります。コンパイラは、この方法で特定のコンテキストにどれだけのスペースが費やされるかを認識しており、スタック フレームを予測できます。動的配列では不可能なので、言語スタッフはそれを禁止することにしました (少なくとも C++11 までは)。

于 2012-06-27T13:05:04.343 に答える
0

char a[5]5sizeof(char)バイトをスタック メモリにnew char[5]割り当て、それらのバイトをヒープ メモリに割り当てます。スタック メモリに割り当てられたバイトは、メモリを明示的に解放する必要があるヒープ メモリとは異なり、スコープの終了時に解放されることも保証されます。

char d[len]変数は const として宣言されているため、コンパイラはこれらのバイトをスタックメモリに割り当てるコードを簡単に作成できるため、許可する必要があります。

于 2012-06-27T13:07:44.067 に答える
0

3 番目の行のペアは機能するはずですが、これはコンパイラ エラーではありません。そこで何か他のことが起こっているに違いない。

最初の 2 つの例の違いは、 のメモリchar a[5];は自動的に解放されるのに対し、 のメモリはchar* b = new char[5];、明示的に解放するまで解放されないことです。最初の方法で割り当てた配列は、特定の変数がスコープ外になると使用できなくなります。これは、そのデストラクタが自動的に呼び出され、メモリが自由に上書きできるためです。を使用して作成された配列newの場合、ポインターを渡し、元の変数のスコープ外で自由に使用できます。また、それが作成されるまでは、それが作成された関数の外でも使用できますdelete

あなたができないことは次のとおりです。

int a = 5;
int *b = new int[a];

動的メモリ割り当ての場合、コンパイル時にサイズがわかっている必要があります。

于 2012-06-27T13:01:53.303 に答える
0

あなたの配列はスタックに割り当てられています。つまり、プログラムがコンパイルされると、a の文字を格納するために 5 バイトを予約する必要があることがわかります。反対に、b は単にポインターとして宣言され、その内容は実行時にヒープに割り当てられますが、メモリが不足している場合は失敗する可能性があります。最後に、be は新しくなったので、ある時点で削除する必要があります。そうしないと、メモリ リークが発生します。

于 2012-06-27T13:02:25.863 に答える
0

new を使用しているときは、フリーストア/ヒープからメモリを割り当てているため、自分で解放する必要があります。また、空きメモリの場所を特定するには、解放するのと同様に、実際には時間がかかる場合があります。

new を使用していない場合、メモリはスタック上で予約され、暗黙的に割り当ておよび解放されます。つまり、関数に入ると、コールスタックはすべてのローカル変数のサイズだけ拡張され (少なくとも概念的には - たとえば、一部の変数は完全にレジスタに存在する可能性があります)、関数を終了すると減少します。

最後の例のように動的サイズの変数をスタックに割り当てる場合、関数スコープに入るときに追加情報が必要であることを意味します。具体的には、予約する必要があるスペースの量は、関数の入力によって異なります。関数の先頭でコンテキストを決定できる場合は、すべて問題ありません。おそらくこれが C99 で許可されている理由です。 「偽の」関数呼び出し。C++ のスコープ規則と合わせると、これは非常に複雑になる可能性があるため、概念的には、std::vector を介して C++ スコープにこれを処理させる方がはるかに簡単です。

于 2012-06-27T13:04:34.407 に答える