C语言中不同变量的访问方式

C语言中的变量大致可以分为全局变量,局部变量,堆变量和静态局部变量,这些不同的变量存储在不同的位置,有不同的生命周期。一般程序将内存分为数据段、代码段、栈段、堆段,这几类变量存储在不同的段中,造成了它们有不同的生命周期。

全局变量

全局变量的生命周期是整个程序的生命周期,随着程序的运行而存在,随着程序的结束而消亡,全局变量位于程序的数据段。每个应用程序有4GB的虚拟地址空间,在程序开始时系统将这个程序加载到内存中,为其分配内存,这个时候,会根据程序文件的内容,为全局变量分配内存,并为之进行初始化,当程序的生命周期结束时,系统回收进程所消耗的资源,这个时候,全局变量所占的内存被销毁。
下面来看一段具体的代码:

1
2
3
4
5
6
int i= 0;
int main(int argc, char* argv[])
{
printf("%d\n", i);
return 0;
}

1
2
3
4
11:       printf("%d\n", i);
00401268 mov eax,[i (00432e24)]
0040126D push eax
0040126E push offset string "%d\n" (0042e01c)

从上述的汇编代码中可以看到,i所对应的地址为0x00432e24,在调用全局变量时,使用的是一个具体的地址,但是并没有看对应初始化i变量的反汇编代码,这是因为在程序开始运行之前,在准备进程环境的时候就为i分配的了存储空间,并进行了初始化。另外在使用时采用的是直接寻址的方式,并没有用寄存器来进行间接寻址,从这点上来看,i变量的地址不会随着程序的运行而改变,这个地址一直可以使用,所以全局变量的生命周期与程序的生命周期相同。

静态变量

静态变量有两个作用,一是将变量名所能使用的区域限定在对应位置,比如我们在一个函数中定义了一个静态变量,那么久只能在这个函数中使用这个变量,二是静态变量的生命周期是全局的,不会随着堆栈环境的改变而改变,下面是一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
int Func()
{
static int i = 0;
i++;
return i;
}

int main()
{
printf("%d\n", Func());
printf("%d\n", Func());
return 0;
}

1
2
3
4
5
6
9:        static int i = 0;
10: i++;
00401268 mov eax,[_Ios_init+3 (00433e24)]
0040126D add eax,1
00401270 mov [_Ios_init+3 (00433e24)],eax
11: return i;

上面的汇编代码也采用的是直接寻址的方式,而这个静态变量的地址为0x433e24,与上面的全局变量的地址进行比较,我们可以看出,其实它也是在全局作用域的,在初始化时也没有发现有任何的初始化代码,所以我们可以说,它的生命周期也是全局的,但是由于static将其可见域限定在函数中,所以在函数外不能通过这个变量名来访问这块内存区域。

##局部静态变量的工作方式
上面说到局部静态变量的生命周期不随函数的结束而结束,不管进入函数多少次,局部静态变量只有一个内存地址,而且只初始化一次,具体编译器是如何做到的,将用下面这一段代码来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int test(int n)
{
static int i = n;
return i;
}

int main(int argc, char* argv[])
{
for (int i = 0; i < 5; i++)
{
printf("%d\n", test(i));
}
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
12:       static int i = n;
00401268 xor eax,eax
0040126A mov al,[`test'::`2'::$S25 (00433e24)];用一个字节存储了一个标志位
0040126F and eax,1
00401272 test eax,eax
00401274 jne test+3Eh (0040128e);当该标志位为1则表明进行了初始化,直接跳过初始化的步骤
00401276 mov cl,byte ptr [`test'::`2'::$S25 (00433e24)]
0040127C or cl,1;没有进行初始化的话,先初始化然后将标志位赋值为1
0040127F mov byte ptr [`test'::`2'::$S25 (00433e24)],cl
00401285 mov edx,dword ptr [ebp+8]
00401288 mov dword ptr [__pInconsistency+39Ch (00433e20)],edx
13: return i;
0040128E mov eax,[__pInconsistency+39Ch (00433e20)]

在上面这段代码中我们企图多次对静态变量进行初始化,但是通过运行程序最终得到的结果都是一样的,上述的代码并没有改变静态变量的值,通过查看汇编代码我们可以看到,编译器在处理局部静态变量时多用了一个字节的内存保存了一个标志位,当该静态变量进行了初始化的时候,就跳过初始化的代码,否则进行初始化并将标志位赋相应的值。

局部变量

局部变量,的生命周期随着函数的调用而存在,当函数结束时它的生命周期就结束了。在我的上一篇将函数的博客中,已经说明了它寻址方式和生命周期。在函数调用时,会首先根据函数中局部变量所占的空间,初始化栈环境,并对这些局部变量进行初始化,当函数调用完成后,会首先回收栈环境,这样局部变量所在的内存被回收,用于下一个函数调用或者用作其他用途,因为栈是动态变化的,为了防止使用不当造成程序错误,所以在函数外是不能使用函数中定义的局部变量。另外一个需要说明的就是在语句块内的局部变量,它的生命周期只在语句块中,但是真实的情况是,它所在的内存与局部变量相同,都是在函数栈中,它的生命周期只在语法层面上进行限制。

#堆变量
堆变量需要程序员自己申请并释放,需要程序员自己管理,程序不会自动管理这些内存,当调用malloc或者new 的时候,系统分配一块内存,直到调用free 或者delete的时候才释放。