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的时候才释放。
C语言中不同变量的访问方式的更多相关文章
- C语言中的数组的访问方式
		
闲下来,写的代码,很是简单,不解释,代码如下: #include <stdio.h> int main(int argc, char **argv) { char cArray[] = & ...
 - smarty中三种变量的访问方式
		
在模板中smarty有三种变量,第一种,php分配的变量,第二种配置文件里的变量,第三种,PHP全局数组里的变量,配置文件里变量的访问方式可以是{#bgcolor#},"#"必须紧 ...
 - Go语言入门(二)Go语言中的变量、常量、数据类型、流程控制以及函数
		
Go语言中的变量 通常用var关键声明变量,有常规方式和简化方式. 常规方式: var name1 type1 name1 = value1 //赋值 简化方式: var name2 = value1 ...
 - Go语言中的变量
		
1 概述 变量(Variable)是程序运行过程中,内容可以变化(修改)的量,变量的功能是存储用户的数据,是计算机语言中能储存计算结果或能表示值抽象概念.变量,是通过变量的标识符定位值的过程.变量的内 ...
 - 【R语言入门】R语言中的变量与基本数据类型
		
说明 在前一篇中,我们介绍了 R 语言和 R Studio 的安装,并简单的介绍了一个示例,接下来让我们由浅入深的学习 R 语言的相关知识. 本篇将主要介绍 R 语言的基本操作.变量和几种基本数据类型 ...
 - C语言中计算变量占用内存空间
		
C语言中计算变量占用内存空间 在C语言中通常用[sizeof]运算符计算变量占内存空间,如下面的例子:
 - C 语言中的变量为什么不能以数字打头
		
C 语言中的变量为什么不能以数字打头? C 语言中的变量为什么不能以数字打头? 不要告诉我编译原理书上有.我暂时看不懂. 除了下面的解释外, “假如变量名允许以数字开头的话,那么语法分析器在解析一个全 ...
 - go语言中在变量后加上接口是什么意思?
		
如题刚刚开始学习go 语言有些不懂: a.Data = make(map[string]interface{}) 我认为它是在申请a.Data map为字符串类型的空间,那么它后面接一个空的inter ...
 - C语言中结构体内存存储方式
		
C语言中结构体内存存储方式 结构体的默认存储方式采用以最大字节元素字节数对其方式进行对齐,例如一个结构体中定义有char.int类型元素,则结构体存储空间按照int类型占用字节,如果还有double类 ...
 
随机推荐
- freemarker四种变量
			
freemarker四种变量 1.简单介绍说明 (1)数据模型中的变量:root中的变量 (2)模板中的变量:使用<#assign>定义的变量 (3)局部变量:在指令中的变量 (4)循环变 ...
 - 【JAVA零基础入门系列】Day7 Java输入与输出
			
[JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...
 - string.prototype.replace 和正则表达式
			
字符串的replace方法是操作字符串的常用方法之一,但这个方法只有当与正则合并使用时,才能体现出它的强大之处. 语法:str.replace(regexp|substr, newsubStr|fun ...
 - Java二维数组的概念和使用方法
			
二维数组 数组的数组---二维数组的每一个元素是一个一维数组 定义格式 数据类型[][] 数组名 = new 数据类型[二维数组的长度/包含的一维数组的个数][每个一维数组的长度]; int[][] ...
 - Intellij IDEA 使用小结
			
快捷键 核心快捷键 IntelliJ IDEA 作为一个以快捷键为中心的 IDE,为大多数操作建议了键盘快捷键.在这个主题中,您可以找到最不可缺少的列表,使 IntelliJ IDEA 轻松实现第一步 ...
 - MySql基础总结
			
1.创建一个表 CREATE TABLE customers ( cust_id INT NOT NULL AUTO_INCREMENT, cust_name CHAR(50) NOT NULL , ...
 - 使用Flink时从Kafka中读取Array[Byte]类型的Schema
			
使用Flink时,如果从Kafka中读取输入流,默认提供的是String类型的Schema: val myConsumer = new FlinkKafkaConsumer08[String](&qu ...
 - CentOS 7 学习(一) 配置LAMP和Nginx
			
CentOS 7 学习(一) 配置LAMP和Nginx CentOS是RedHat Linux企业版的代码编译版本,属于比较通用的服务器Linux版本,据说Ubuntu Server更通用,呵呵,不过 ...
 - std::shared_ptr<void>的工作原理
			
前戏 先抛出两个问题 如果delete一个指针,但是它真实的类型和指针类型不一样会发生什么? 是谁调用了析构函数? 下面这段代码会发生什么有趣的事情? // delete_diff_type.cpp ...
 - ios 去掉字符串中的空格 和指定的字符
			
[问题分析] .使用NSString中的stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]方法只是去掉左右 ...