C的变量类型、作用域与生命周期的总结

C的变量类型、作用域与生命周期的总结

最近在看“C Programing Language" (Kernighan, Ritchie)关于外部变量的讨论,之前在学C的时候对这些extern, auto, static, register等不是太理解,这本书讲的很详细,现在总结一下。

首先, C的变量分成局部变量 local variable 和全局变量 global variable。

【注】

  1. C 中局部(local)变量(也有翻译成本地变量),也可以叫做内部(internal)变量
  2. C 中全局(global)变量,又可叫外部(external)变量。

这些称呼都可以互换,不同的称呼可能强调的是不同的特性,以下尽量用对应使用的关键字来称呼,比如局部变量将用自动变量(auto)来称呼,全局变量将用外部变量(extern)来称呼。

C语言程序可以看成是由许多外部对象构成的,这些外部对象可以是变量或函数。外部(external)和内部(internal)是相对的,internal是用来描述在函数内部的函数参数或变量,external描述的是定义在函数外部的变量。由于C语言不允许在函数内部定义函数,因此函数都可以看成是外部的(extern)。

栗子:

#include <stdio.h>
#define MAX 100
char s[MAX];
void printString(void)
{
    printf("%s", s);
}

int main(void)
{
    scanf("%s", s);
    printString(s);
    return 0;
}

这个简单的printString.c源文件即可看成是由三个外部对象构成:外部变量s, 外部函数printString()main()组成,还有一个include的标准库文件stdio.h(这个下次再谈)。

默认情况下,外部变量与函数具有以下性质:通过同一个名字引用的所有外部变量(即使这种引用来自单独编译的不同函数)实际上都是引用内存中同一个对象。

因为外部变量可以在全局范围内访问,这就为函数之间的数据交换提供一种新的方式,可以代替函数参数与返回值,如上一个栗子。任何函数都可以通过名字访问一个外部变量,当然这个名字需要通过某种方式来声明。

外部变量与自动变量的作用域与生命周期

  • 变量或者符号的作用域是指程序中可以使用这个变量或名字的范围。这个可以看成是静态的代码范围
  • 变量生命周期则是指该变量存在的时间范围。这个可以理解为程序运行时的变量存在的时间周期。

自动变量只能在函数内部使用,作用域从声明处开始直至函数结束,生命周期是从其所在的函数被调用时,变量开始存在,在函数退出时变量将消失。对于在函数开头声明的自动变量,其作用域即为声明该变量名的函数内部,函数的参数也是如此,实际上可以将它看作是这个函数的局部变量。当然,自动变量也可以定义在函数内的语句块中,比如在下面的for循环中定义的临时变量temp:

int func(void)
{
    int i;
    for(i = 0; i < 10; i++)
    {
        int temp;
        ...
    }
}

这种变量当然作用域是限定在语句块内部,生命周期也是在该语句块内部,当程序执行完该语句块,变量也就消失了。

外部变量作用域为从其定义处开始直至所在的文件的结尾结束,生命周期是永久存在的,即程序执行期间一直存在,它们的值在一次函数调用结束到下一次函数调用开始之前保持不变。另一方面,可以通过添加声明的方式来使用外部变量,按照上面所说,外部变量是唯一的,因此添加extern声明可以扩展外部变量的作用域到其他文件中。

如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个文件中,则必须在相应的变量声明中强制使用关键字extern

将外部变量的定义与声明区别开是很有必要的,外部变量的声明用于说明变量的属性(主要是类型),而外部变量的定义除此之外还会引起内存的分配(在定义后编译程序将为它分配内存单元)。

而自动变量则不然,自动变量在C中没有定义这一说法,只要先声明再使用即可,这是因为自动变量(即局部变量)是在运行时由栈来管理的,而外部变量(即全局变量)是在编译过程中由编译器、汇编器分配存储地址,一直到链接时确定内存位置(这些内容将在之后会专门总结有关编译、汇编、链接的内容),在这里都可以理解为在编译时即分配了内存单元。

栗子:如果将下面这两条语句放在所有函数的外部:

int a;
double b[MAX];

则这两条语句将定义外部变量a与b,并为之分配内存,同时这两条语句还可以作为该源文件中其余部分的声明。而下面的两行语句:

extern int a;
extern double b[];

为所在文件该语句之后的部分声明了一个int类型的外部变量a以及一个double类型的外部变量b(该数组的长度在其他地方确定),但这两个声明并没有建立变量或为他们分配内存。

在程序的源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明)。外部变量的定义必须指定数组的长度,但extern声明则不一定要指定数组的长度。外部变量的初始化只能出现在其定义中(注:若外部变量未初始化,编译器将它初始化为0)。

【注】外部变量的声明也可以通过上下文隐式声明(即如上所说,定义即可作为之后语句的声明)如下面程序版本2中的外部变量lenbuf在main函数中就无需在main中再声明。

版本1:

#include <stdio.h>
#define MAXLENGTH 1000 // buffer最大长度 

char buf[MAXLENGTH];

int getline(void);

/*一个简单的copy-paste程序
*/
int main(void)
{
    while (getline()!=EOF)
    {
        printf("%s\n", buf);
    }
    return 0;
}

int getline(void)
{
    int c, i;
    extern char buf[MAXLENGTH];

    i= 0;
    while ( i < MAXLENGTH && (c = getchar()) != EOF && c != ‘\n‘)
        buf[i++] = c;
    if (c = ‘\n‘)
        buf[i++] = c;
    buf[i] = ‘\0‘;
    return i;
}

版本2:

#include <stdio.h>
#define MAXLENGTH 1000 // buffer最大长度 

char buf[MAXLENGTH];

int getline(void);

/*一个简单的copy-paste程序
*/
int main(void)
{
    while (getline()!=EOF)
    {
        printf("%s\n", buf);
    }
    return 0;
}

int getline(void)
{
    int c, i;
    // 通过上下文隐式声明 buf[]

    i= 0;
    while ( i < MAXLENGTH && (c = getchar()) != EOF && c != ‘\n‘)
        buf[i++] = c;
    if (c = ‘\n‘)
        buf[i++] = c;
    buf[i] = ‘\0‘;
    return i;
}

静态变量与寄存器变量

静态变量

之前已经提到了,外部变量与自动变量,其中外部变量是可以被全局使用的,这个全局指的是整个源程序的所有源文件都可以通过添加extern声明来使用。但是,如果我们希望限定这个外部变量仅限于该定义的源文件使用,而不希望被其他源文件使用。那我们可以使用static声明限定外部变量和函数,可以将其声明的对象的作用域限定为该源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。

static char buf[BUFSIZE];
static int bufp = 0;

变量声明为static之后,该变量即为静态存储,其他文件中的函数就不可以访问变量buf, bufp,因此这两个名字就不会和同一程序中的其他文件中相同名字的变量相冲突。

【注】:多个函数中的自动变量同名,也不会造成冲突,因为在编译过程中,编译器会将自动变量改成不同名字,比如加上函数名,具体做法依赖于编译器版本。

外部的static声明多用于变量,当然,也可以用于声明函数。通常情况下,函数名是全局可访问的,对整个程序的各个部分都是可见的。但是,如果把函数声明为static类型,则该函数除了对该函数声明所在的文件可见外,其他文件都无法访问。

static也可用于声明自动变量,static类型的自动变量同一般的自动变量一样,是某个特定函数内的局部变量,只能在该函数中使用。但它与一般的自动变量不同的是,不管其所在函数是否被调用,它一直存在。换句话说,static类型的内部变量作用域不变,生命周期和外部变量一样为整个程序运行期间。

寄存器变量

register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想史,将register变量放在机器的寄存器中,这样可以使程序更小,执行速度更快。

register声明的形式如下:

register int x;
register char c;

register声明只适用于自动变量以及函数的形式参数,看下面的例子:

void f(register unsigned a, register long n)
{
    register int i;
    ...
}

实际使用的时候,底层硬件环境会对寄存器变量的使用有一些限制。每个函数中只有很少的变量可以保存在寄存器中,且只允许某些类型的变量。但是,过量的寄存器变量并没有什么害处,因为编译器可以忽略过量的或不支持的寄存器变量声明。另外,无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。

总结

变量类型 定义 作用域 生命周期 说明
自动变量 定义在函数内部或者是函数参数 自声明其至函数结尾或者是所在语句块结尾 作用域生效则生效,作用域失效则失效,多次调用,重新创建该变量 在运行时,由栈管理
外部变量 定义在函数外部或者是函数 自定义起至所在文件结尾,可通过extern声明,扩展至全局 整个程序运行期间 在编译时,一旦定义即创建变量、分配内存
静态变量 声明时使用,通过添加static来声明 声明外部变量时,该外部变量作用域仅为声明所在文件,声明自动变量时,不改变 整个程序运行期间 可以限定全局变量,函数或者是自动变量
寄存器类型 通过添加register声明 - - 只能限定自动变量或函数参数,可能被存放在寄存器中,也可能被忽略,但是被声明为寄存器类型的变量地址不可访问

因此,可以将变量分为:被初始化的全局范围的外部变量,被初始化的静态类型外部变量,未被初始化的两类外部变量,自动变量,静态类型自动变量,寄存器类型自动变量。

至于为什么这么分,下次讨论编译的时候用的上。

原文地址:https://www.cnblogs.com/deepC/p/12620083.html

时间: 2024-08-24 18:40:14

C的变量类型、作用域与生命周期的总结的相关文章

C/C++——C++变量的作用域与生命周期,C语言中变量的作用域和生命周期

全局变量 作用域:全局作用域(全局变量只需在一个源文件中定义,就可以作用于所有的源文件.) 生命周期:程序运行期一直存在 引用方法:其他文件中要使用必须用extern 关键字声明要引用的全局变量. 内存分布:全局数据区 注意:如果在两个文件中都定义了相同名字的全局变量,连接出错:变量重定义 全局静态变量 作用域:文件作用域(只在被定义的文件中可见.) 生命周期:程序运行期一直存在 内存分布:全局数据区 定义方法:static关键字,const 关键字 注意:只要文件不互相包含,在两个不同的文件中

《征服 C 指针》摘录2:C变量的 作用域 和 生命周期(存储期)

在开发一些小程序的时候,也许我们并不在意作用域的必要性.可是,当你书写几万行,甚至几十万行的代码的时候,没有作用域肯定是不能忍受的. C 语言有如下 3 种作用域. 1.全局变量 在函数之外声明的变量,默认地会成为全局变量.全局变量在任何地方都是可见的.当程序被分割为多个源代码文件进行编译时,声明为全局变量的变量也是可以从其他源代码文件中引用的. 2.文件内部的静态变量 就算对于像全局变量那样被定义在函数外面的变量,一旦添加了 static,作用域就只限定在当前所在的源代码文件中.通过 stat

C++中的作用域与生命周期

Pascal之父Nicklaus Wirth曾经提出一个公式,展示出了程序的本质:程序=算法+数据结构.后人又给出一个公式与之遥相呼应:软件=程序+文档.这两个公式可以简洁明了的为我们展示程序和软件的组成. 程序的运行过程可以理解为算法对数据的加工过程,程序的运行的结果,就是算法加工数据产生的结果数据.算法描述的是对数据加工的步骤,对应于程序中的函数.数据结构描述的是数据在计算机中的组织结构,对应于程序中的数据类型.程序中数据对应的就是无处不在变量.对于我们编程人员,面对的无非就是函数,数据类型

嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性

嵌入式 Linux C语言(八)--存储类型.作用域.生命周期.链接属性 一.存储类型 C语言中,每个变量和函数都有两个属性:数据类型和数据的存储类型. 变量的存储类型是指存储变量值的内存类型.变量的存储类型决定变量何时创建.何时销毁以及它的值将保持多久.计算机中有三个地方可以用于存储变量:普通内存,运行时堆和栈,硬件寄存器.变量的存储类型取决于声明变量的位置. C语言存储类别说明符: 说明符 用    法 auto 只在代码块内变量声明中被允许, 表示变量具有本地生存期 extern 出现在顶

文件载入流程,函数的使用【函数名,参数【按引用赋值的问题】,函数体,【变量作用域和生命周期】】

1.求最大公约数辗转相除求值例如:12,8求最大公约数12%8=4[不为零]8%4=0[为零,则4为最大公约数][原理就是12,8的最大公约数和8,4的最大公约数一致][这个问题已经可被证明]计算机中最早的算法 2.文件载入---流程[php执行的先后顺序][php对代码的编译以文件为单位]先检查一个文件中的语法再进行编译然后才会一行行解释执行 [这一点需要注意]这里进行文件载入的流程讲解:php文件载入时,被引入的文件中的语法并不会解析,只有当该文件被执行[也就是被引入语句之后的部分才会报错]

存储类、作用域、生命周期、链接属性

1.linux下C语言程序的内存映像代码段(.text).数据段(.data).bss段.栈.堆的概念 代码段(.text) (1)对应着程序中的代码(函数),代码段在linux中又叫文本段(.text)(2)部分平台下的const修饰的变量. 数据段(.data) 1.显式初始化为非0的全局变量: 2.显式初始化为非0的static局部变量 bss段 1.显式初始化为0或者未显式初始化的全局变量: 2.显式初始化为0或未显式初始化的static局部变量. 栈 局部变量分配在栈上:函数调用传参过

7、C_存储类 &amp;amp; 作用域 &amp;amp; 生命周期 &amp;amp; 链接属性

概念解析 存储类 存储类就是存储类型,也就是描述C语言变量在何种地方存储. 内存有多种管理方法:栈.堆.数据段.bss段..text段······一个变量的存储类属性就是描述这个变量存储在何种内存段中. 譬如:局部变量分配在栈上,所以它的存储类就是栈:显式初始化为非0的全局变量分配在数据段,显式初始化为0和没有显示初始化(默认为0)的全局变量分配在bss段. 作用域 作用域是描述这个变量起作用的代码范围. 基本来说,C语言变量的作用域规则是代码块作用域.意思就是这个变量起作用的范围是当前的代码块

spring作用、spring注解、管理对象的作用域与生命周期、自动装配

Spring 1. 作用 创建和管理对象,使得开发过程中,可以不必使用new关键字创建对象,而是直接获取对象!并且,还可以通过一些配置,使得某些获取到的对象,其中某些属性已经是被赋值的! 2. Spring注解 在Spring中,定义了一系列的注解,可以取代几乎所有的XML配置! 尽管使用注解可以完成此前的许多配置,但是,基于Spring的项目仍需要Spring的配置文件! 2.1. 常用注解 使用注解的方式来创建和管理对象,首先,必须在Spring的配置文件中添加组件扫描: <!-- 组件扫描

MyBatis 作用域和生命周期

理解到目前为止所讨论的类的作用域和生命周期是非常重要的.如果使用不当可导致严重的并发性问题. SqlSessionFactoryBuilder  这个类可以在任何时候被实例化.使用和销毁.一旦您创造了SqlSessionFactory 就不需要 再保留它了.所以SqlSessionFactoryBuilder 实例的最好的作用域是方法体内(即一个本地方法 变量).您能重用SqlSessionFactoryBuilder 创建多个SqlSessionFactory 实例,但最好不要把 时间.资源放