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

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

全局变量

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

int i= 0;
int main(int argc, char* argv[])
{
    printf("%d\n", i);
    return 0;
}
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变量的地址不会随着程序的运行而改变,这个地址一直可以使用,所以全局变量的生命周期与程序的生命周期相同。

静态变量

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

int Func()
{
    static int i = 0;
    i++;
    return i;
}

int main()
{
    printf("%d\n", Func());
    printf("%d\n", Func());
    return 0;
}
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将其可见域限定在函数中,所以在函数外不能通过这个变量名来访问这块内存区域。

局部静态变量的工作方式

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

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;
}
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的时候才释放。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ppjun专栏

Multiple substitutions specified in non-positional format

as3.0以上在gradle.properties使用android.enableAapt2=true,as就会提示将要过期了,请设置成 android.en...

66430
来自专栏大闲人柴毛毛

三分钟理解“状态模式”——设计模式轻松掌握

什么是状态模式? 一个函数原本有很多判断语句,现在把判断语句中的每一种状态封装成一个类,每一个状态类中均有一个handle()函数,该函数能对当前状态做出处理,...

80470
来自专栏章鱼的慢慢技术路

Linux操作_grep/egrep工具的使用

16570
来自专栏MasiMaro 的技术博文

C 堆内存管理

在Win32 程序中每个进程都占有4GB的虚拟地址空间,这4G的地址空间内部又被分为代码段,全局变量段堆段和栈段,栈内存由函数使用,用来存储函数内部的局部变量,...

18820
来自专栏C/C++基础

C++名字空间详解

名字空间(namespace)是由标准C++引入的,是一种新的作用域级别。原来C++标识符的作用域分为三级:代码块({…}和函数体)、类域和全局作用域。如今,在...

7310
来自专栏C/C++基础

C++中cin的详细用法

cin是C++编程语言中的标准输入流对象,即istream类的对象。cin主要用于从标准输入读取数据,这里的标准输入,指的是终端的键盘。此外,cout是流的对象...

45430
来自专栏python3

python Json与pickle数据序列化

在程序运行的过程中,所有的变量都是在内存中。一旦程序结束,变量所占用的内存就被操作系统全部回收。

15210
来自专栏武军超python专栏

2018年7月22日用python写个人博客时遇到的问题

今天遇到的新单词: subscript  n下标,脚注 integer    n整数,整型 function   n函数 variable   n变量 ...

11820
来自专栏爱撒谎的男孩

多线程的使用

18350
来自专栏向治洪

React 语法之let和const命令

let命令 基本用法 ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 { let a = ...

3.5K60

扫码关注云+社区

领取腾讯云代金券