变量存储的位置和方式的确切细节取决于实现。语言定义仅指定变量的可见性和生命周期,而不是存储它们的机制。
我们将看一个特定的实现——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
或在文件范围(任何函数之外)声明的变量具有 storage class static
,这意味着这些变量的内存在程序启动时分配并保留到程序退出。在块中声明且没有static
关键字的变量具有存储类auto
,这意味着它们只存在于该块中1。 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 (0xbffffe4e0)
显然,在这个实现中,具有静态范围的变量gvar
和avar
存储的变量与具有自动范围的变量完全不同,比如avar
和x
。
这是生成的汇编代码的列表(减去一些分页符):
1 .文件“storage.c”
2.版本“01.01”
5.文本
6.Ltext0:
第165章
第166章
第168章 4
171 克瓦尔:
172 0000 01000000 .long 1
第173章 4
176 变量.0:
177 0004 02000000 .long 2
第178章
第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 **** #include
2:存储.c ****
3:storage.c **** int gvar = 1;
4:存储.c ****
5:storage.c **** int main(void)
6:存储.c **** {
189 .LM1:
190.LBB2:
191 0000 55 推升 %ebp
192 0001 89E5 移动 %esp, %ebp
193 0003 83EC08 subl $8, %esp
7:storage.c **** 静态 int svar = 2;
8:storage.c **** int avar = 3;
195 .LM2:
196 0006 C745FC03 移动 $3, -4(%ebp)
196 000000
9:存储.c ****
10:storage.c **** 做{
11:storage.c **** int avar2 = 4;
198 .LM3:
199 .LBB3:
200 000d C745F804 movl $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 pushl -8(%ebp)
207 001e 8D45FC leal -4(%ebp), %eax
208 0021 50 推力 %eax
209 0022 FF75FC pushl -4(%ebp)
210 0025 68040000 pushl $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 调用 printf
215 法郎
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:存储.c ****
19:storage.c **** 返回 0;
221 .LM6:
222 0048 B8000000 移动 $0, %eax
222 00
20:storage.c **** }
224 .LM7:
225 .LBE2:
226 004d C9 离开
227 004e C3 RET
第228章
第237章
第239章
241. 正文:
242 004f 90 .ident“GCC:(GNU)2.96 20000731(Red Hat Linux 7.2 2.96-112.7.2)”
有很多东西要费力,但它告诉我们为什么我们在内存地址上会出现如此大的差异。
线条
5.文本
6.Ltext0:
第165章
第166章
第168章 4
171 克瓦尔:
172 0000 01000000 .long 1
第173章 4
176 变量.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 movl $4, -8(%ebp)
200 000000
告诉我们 和 的内存avar
相avar2
对于当前帧指针(存储在 中%ebp
);avar
从帧指针偏移 4 个字节存储,而avar2
从帧指针偏移 8 个字节存储。
同样,这就是一个操作系统上的一个编译器的一个版本是如何做到的;不同操作系统上的不同编译器可能使用不同的结构(尽管这种方法很常见)。
如果你真的很想知道确切的细节,你会想要了解更多关于你的架构(x86 vs. Power vs. MIPS vs...),并且你会想做一些汇编编程。
1.注意,这并不一定意味着每次进入和退出块时都会分配和释放内存;通常,相同的内存位置将被重用,但您不能保证它们具有与退出块时相同的值。