回答:
いいえ、ブレースはスタックフレームとして機能しません。Cでは、中かっこは名前付けスコープを示すだけですが、制御が渡されても、何も破壊されず、スタックからポップされるものもありません。
プログラマーがコードを書くとき、それをスタックフレームであるかのように考えることがよくあります。中かっこ内で宣言された識別子は中かっこ内でのみアクセスできるため、プログラマーの観点からは、宣言時にスタックにプッシュされ、スコープが終了するとポップされるようです。ただし、コンパイラーは、入口/出口で何かをプッシュ/ポップするコードを生成する必要はありません(通常、そうではありません)。
また、ローカル変数はスタックスペースをまったく使用しない場合があることに注意してください。これらは、CPUレジスターまたは他の補助記憶域の場所に保持されるか、完全に最適化されます。
したがって、d
理論的には、配列は関数全体のメモリを消費する可能性があります。ただし、コンパイラはそれを最適化するか、使用寿命が重複しない他のローカル変数とメモリを共有します。
変数が実際に存在する時間メモリを使用している時間は、明らかにコンパイラに依存します(多くのコンパイラは、関数内で内部ブロックに出入りするときにスタックポインタを調整しません)。
ただし、密接に関連しているが、おそらくもっと興味深い質問は、プログラムが内部スコープの外側(ただし、包含関数内)でその内部オブジェクトにアクセスできるかどうかです。
void foo() {
int c[100];
int *p;
{
int d[200];
p = d;
}
/* Can I access p[0] here? */
return;
}
(言い換えると、実際にはほとんど割り当てられてd
いない場合でも、コンパイラは割り当て解除できますか?)
答えは、コンパイラが割り当てを解除することが許可されていることであり、コメントが示す場所d
へのアクセスp[0]
は未定義の動作です(プログラムは内部スコープ外の内部オブジェクトへのアクセスを許可されていません)。C標準の関連部分は6.2.4p5です。
可変長配列型を持たないそのようなオブジェクト(自動保存期間を持つオブジェクト)の場合、その寿命は、関連付けられているブロックのエントリから、 そのブロックの実行が何らかの形で終了するまで続きます。(囲まれたブロックに入るか、関数を呼び出すと、現在のブロックの実行が中断されますが、終了しません。)ブロックが再帰的に入力されると、オブジェクトの新しいインスタンスが毎回作成されます。オブジェクトの初期値は不定です。オブジェクトに初期化が指定されている場合、ブロックの実行で宣言に到達するたびに初期化が実行されます。それ以外の場合、値は宣言に到達するたびに不確定になります。
あなたの質問は明確に答えられるほど明確ではありません。
一方では、コンパイラは通常、ネストされたブロックスコープに対してローカルメモリの割り当てと割り当て解除を行いません。ローカルメモリは通常、関数の入口で一度だけ割り当てられ、関数の出口で解放されます。
一方、ローカルオブジェクトの存続期間が終了すると、そのオブジェクトが占有していたメモリは、後で別のローカルオブジェクトに再利用できます。たとえば、このコードでは
void foo()
{
{
int d[100];
}
{
double e[20];
}
}
通常、両方の配列は同じメモリ領域を占有します。つまり、関数foo
が必要とするローカルストレージの総量は、同時に両方ではなく、2つの配列の最大のものに必要なものです。
後者d
があなたの質問の文脈で機能の終わりまでメモリを占有し続けるとみなされるかどうかはあなたが決めることです。
実装に依存します。私はgcc 4.3.4が何をするかをテストする短いプログラムを書きました、そしてそれは関数の開始時にすべてのスタック空間を一度に割り当てます。-Sフラグを使用して、gccが生成するアセンブリを調べることができます。
いいえ、Dは[]うないルーチンの残りのスタック上にあります。しかし、alloca()は異なります。
編集:クリストファージョンソン(およびサイモンとダニエル)は正しいです、そして私の最初の応答は間違っていました。gcc 4.3.4。のCYGWINでは、コードは次のとおりです。
void foo(int[]);
void bar(void);
void foobar(int);
void foobar(int flag) {
if (flag) {
int big[100000000];
foo(big);
}
bar();
}
与える:
_foobar:
pushl %ebp
movl %esp, %ebp
movl $400000008, %eax
call __alloca
cmpl $0, 8(%ebp)
je L2
leal -400000000(%ebp), %eax
movl %eax, (%esp)
call _foo
L2:
call _bar
leave
ret
生活し、学びます!そして簡単なテストは、AndreyTが複数の割り当てについても正しいことを示しているようです。
後で追加:上記のテストはgccのドキュメントが正しくないことを示しています。何年もの間それは言った(強調は加えられた):
「可変長配列のスペースは、配列名のスコープが終了するとすぐに割り当て解除されます。」
alloca
関数を呼び出しません。cygwin gccがそれを行うことに本当に驚いています。それは可変長配列でさえないので、IDKがそれを提示する理由です。
d
通常、変数はスタックからポップされません。中括弧はスタックフレームを意味しません。そうしないと、次のようなことはできません。
char var = getch();
{
char next_var = var + 1;
use_variable(next_char);
}
中括弧が(関数呼び出しのように)真のスタックプッシュ/ポップを引き起こした場合、中括弧内のコードは中括弧のvar
外にある変数にアクセスできないため、上記のコードはコンパイルされません(サブ関数は呼び出し元の関数の変数に直接アクセスできません)。これは事実ではないことを知っています。
中かっこは単にスコープの目的で使用されます。コンパイラーは、囲み括弧の外側からの「内部」変数へのアクセスをすべて無効として扱い、そのメモリーを他のものに再利用する場合があります(これは実装に依存します)。ただし、囲んでいる関数が戻るまで、スタックからポップされない場合があります。
更新:C仕様 の内容は次のとおりです。自動保存期間を持つオブジェクトについて(セクション6.4.2):
可変長配列型を持たないオブジェクトの場合、その存続期間は、エントリが関連付けられているブロックのエントリから、そのブロックの実行がとにかく終了するまで続きます。
同じセクションでは、「ライフタイム」という用語を(私の強調)と定義しています。
オブジェクトの存続期間は、プログラム実行の一部であり、その間にストレージが確保されることが保証されています。オブジェクトは存在し、一定のアドレスを持ち、その存続期間を通して最後に格納された値を保持します。オブジェクトがその存続期間外で参照された場合の動作は未定義です。
もちろん、ここでのキーワードは「保証」です。中かっこの内側のセットのスコープを離れると、配列の有効期間は終了します。ストレージはまだ割り当てられている場合とされていない場合がありますが(コンパイラーがスペースを別の目的で再利用する場合があります)、配列にアクセスしようとすると、未定義の動作が発生し、予期しない結果が生じます。
C仕様には、スタックフレームの概念はありません。結果のプログラムがどのように動作するかについてのみ説明し、実装の詳細はコンパイラに任せます(結局のところ、実装はスタックレスCPUの場合とハードウェアスタックのCPUの場合とでかなり異なります)。C仕様には、スタックフレームが終了する場所または終了しない場所を義務付けるものはありません。唯一の本当のに知る方法は、特定のコンパイラ/プラットフォームでコードをコンパイルし、結果のアセンブリを調べることです。コンパイラの現在の最適化オプションのセットは、おそらくこれにも役割を果たすでしょう。
あなたは配列があることを確認しないようにしたい場合はd
、もはやあなたのコードの実行中にメモリを食べている、あなたはどちらか別の関数または明示的に中括弧内のコードを変換することができますmalloc
し、free
メモリの代わりに、自動ストレージを使用しました。
私はそれが範囲外になると信じていますが、関数が戻るまでスタックからポップされません。そのため、関数が完了するまでスタック上のメモリを占有しますが、最初の閉じ中かっこの下流ではアクセスできません。
この規格については、実装に固有であることを示す多くの情報がすでに提供されています。
したがって、1つの実験が興味深いかもしれません。次のコードを試してみます。
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
printf("%p\n", (void*) x);
}
{
int b;
y = &b;
printf("%p\n", (void*) y);
}
}
gccを使用して、同じアドレスを2回取得します。Coliro
しかし、次のコードを試してみます。
#include <stdio.h>
int main() {
int* x;
int* y;
{
int a;
x = &a;
}
{
int b;
y = &b;
}
printf("%p\n", (void*) x);
printf("%p\n", (void*) y);
}
我々はここで2つの異なるアドレスを取得gccの使用:Coliroを
だから何が起こっているのか本当にわからない。