変数が格納される場所と方法の正確な詳細は、実装次第です。言語定義は、変数の可視性と有効期間のみを指定し、変数が格納されるメカニズムは指定しません。
Red Hat 7.2 上の gcc 2.96 という特定の実装を見ていきます。
簡単なソース ファイルを次に示します。
#include <stdio.h>
int gvar = 1; // file scope, static extent
int main(void)
{
static int svar = 2; // block scope, static keyword => static extent
int avar = 3; // block scope, auto extent
do {
int avar2 = 4; // block scope, auto extent
printf("gvar = %d (%p)\nsvar = %d (%p)\navar = %d (%p)\navar2 = %d (%p)\n",
gvar, (void *) &gvar,
svar, (void *) &svar,
avar, (void *) &avar,
avar2, (void *) &avar2);
} while (0);
return 0;
}
キーワードstatic
またはファイル スコープ (関数の外部) で宣言された変数には、記憶域クラスがstatic
あります。つまり、これらの変数のメモリは、プログラムの起動時に割り当てられ、プログラムが終了するまで保持されます。static
ブロック内でキーワードなしで宣言された変数にはストレージ クラスがあります。つまり、変数はauto
そのブロック内にのみ存在します。 avar2
ループの外で名前で参照することはできずdo
、ループの終了後にその値を保持することは保証されていません。
プログラムをコンパイルします
gcc -o storage -g storage.c -ansi -pedantic -Wall -Werror -Wa,-aldh=storage.lst.redhat
は-Wa,-alhd=...
、生成されたマシン コードのリストを指定されたファイルに書き込みます。デバッグ フラグを追加したため-g
、元の C ソースがマシン コードにインターリーブされます。
プログラムからの出力は次のとおりです。
gvar = 1 (0x80495b0)
svar = 2 (0x80495b4)
avar = 3 (0xbfffe4e4)
avar2 = 4 (0xbfffe4e0)
gvar
明らかに、この実装では、およびのような静的エクステントを持つ変数は、およびavar
のような自動エクステントを持つ変数とはまったく異なる場所に格納されます。 avar
x
生成されたアセンブリ コードのリストを次に示します (一部の改ページを除く)。
1 .ファイル「storage.c」
2 .バージョン「01.01」
5 .テキスト
6 .Ltext0:
165 .globl gvar
166 .データ
168 .アライン 4
171 グヴァル:
172 0000 01000000 .ロング 1
173 .アライン 4
176 svar.0:
177 0004 02000000 .long 2
178 .セクション .rodata
179 .アライン 32
180 .LC0:
181 0000 67766172 .string "gvar = %d (%p)\nsvar = %d (%p)\nvar = %d (%p)\navar2 = %d (%p)\n"
181 2020203D
181 20256420
181 28257029
181 0A737661
182 0044 00000000 .テキスト
182 00000000
182 00000000
182 00000000
182 00000000
183 .アライン 4
185 .グローバルメイン
187 メイン:
1:storage.c **** #インクルード
2:storage.c ****
3:storage.c **** int gvar = 1;
4:storage.c****
5:storage.c **** int main(void)
6:storage.c **** {
189 .LM1:
190 .LBB2:
191 0000 55 プシュル %ebp
192 0001 89E5 movl %esp, %ebp
193 0003 83EC08 subl $8、%esp
7:storage.c **** static int svar = 2;
8:storage.c **** int avar = 3;
195 .LM2:
196 0006 C745FC03 移動 $3, -4(%ebp)
196 000000
9:storage.c****
10:storage.c **** ド {
11:storage.c **** int avar2 = 4;
198 .LM3:
199 .LBB3:
200 000d C745F804 移動 $4, -8(%ebp)
200 000000
12:storage.c **** printf("gvar = %d (%p)\nsvar = %d (%p)\nvar = %d (%p)\navar2 = %d (%p)\n" 、
202 .LM4:
203 0014 83EC0C subl $12、%esp
204 0017 8D45F8 leal -8(%ebp), %eax
205 001a 50 プシュル %eax
206 001b FF75F8 プッシュル -8(%ebp)
207 001e 8D45FC leal -4(%ebp), %eax
208 0021 50 プシュル %eax
209 0022 FF75FC プッシュ -4(%ebp)
210 0025 68040000 プッシュル $svar.0
210 00
211 002a FF350400 pushl svar.0
211 0000
212 0030 68000000 プッシュル $gvar
212 00
213 0035 FF350000 pushl gvar
213 0000
214 003b 68000000 プッシュ $.LC0
214 00
215 0040 E8FCFFFF call printf
215FF
216 0045 83C430 追加 $48、%esp
13:storage.c **** gvar, (void *) &gvar,
14:storage.c **** svar, (void *) &svar,
15:storage.c **** avar, (void *) &avar,
16:storage.c **** avar2, (void *) &avar2);
17:storage.c **** } while (0);
218 .LM5:
219 .LBE3:
18:storage.c****
19:storage.c **** リターン 0;
221 .LM6:
222 0048 B8000000 movl $0, %eax
222 00
20:ストレージ.c****}
224 .LM7:
225 .LBE2:
226 004d C9 休暇
227 004e C3 ret
228 .Lfe1:
237 .Lscope0:
239.テキスト
241 .Letext:
242 004f 90 .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.2 2.96-112.7.2)"
苦労することはたくさんありますが、メモリ アドレスにこのような違いが生じる理由がわかります。
台詞
5 .テキスト
6 .Ltext0:
165 .globl gvar
166 .データ
168 .アライン 4
171 グヴァル:
172 0000 01000000 .ロング 1
173 .アライン 4
176 svar.0:
177 0004 02000000 .long 2
gvar
とのメモリは、プログラム コードが存在するプログラム イメージ svar
のセクションにあることを教えてください。.text
線を比較すると
195 .LM2:
196 0006 C745FC03 移動 $3, -4(%ebp)
196 000000
...
198 .LM3:
199 .LBB3:
200 000d C745F804 移動 $4, -8(%ebp)
200 000000
avar
とのメモリavar2
が現在のフレーム ポインタ ( に格納されている) を基準に配置されていることを教えてください%ebp
。avar
はフレーム ポインタから 4 バイト オフセットで格納され、avar2
はフレーム ポインタから 8 バイト オフセットで格納されます。
繰り返しになりますが、これは、1 つのオペレーティング システム上の 1 つのコンパイラの 1 つのバージョンで行う方法です。異なるオペレーティング システム上の異なるコンパイラは、異なる構造を使用する場合があります (ただし、このアプローチは非常に一般的です)。
正確な詳細を知りたくてうずうずしている場合は、アーキテクチャ (x86 対 Power 対 MIPS 対 ...) についてさらに学びたいと思うでしょう。また、アセンブリ プログラミングを実行したいと思うでしょう。
1. これは、ブロックに出入りするたびにメモリが割り当てられ、割り当てが解除されることを必ずしも意味しないことに注意してください。通常、同じメモリ位置が再利用されますが、ブロックが終了したときと同じ値を持つことは保証できません。