C的变量类型、作用域与生命周期的总结
C的变量类型、作用域与生命周期的总结
最近在看“C Programing Language" (Kernighan, Ritchie)关于外部变量的讨论,之前在学C的时候对这些extern, auto, static, register等不是太理解,这本书讲的很详细,现在总结一下。
首先, C的变量分成局部变量 local variable 和全局变量 global variable。
【注】
C 中局部(
local)变量(也有翻译成本地变量),也可以叫做内部(internal)变量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中的外部变量len和buf在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声明 |
- | - | 只能限定自动变量或函数参数,可能被存放在寄存器中,也可能被忽略,但是被声明为寄存器类型的变量地址不可访问,即取地址运算符&不可进行运算 |
因此,可以将变量分为:被初始化的全局范围的外部变量,被初始化的静态类型外部变量,未被初始化的两类外部变量,自动变量,静态类型自动变量,寄存器类型自动变量。
至于为什么这么分,下次讨论编译的时候用的上。
C的变量类型、作用域与生命周期的总结的更多相关文章
- C/C++——C++变量的作用域与生命周期,C语言中变量的作用域和生命周期
全局变量 作用域:全局作用域(全局变量只需在一个源文件中定义,就可以作用于所有的源文件.) 生命周期:程序运行期一直存在 引用方法:其他文件中要使用必须用extern 关键字声明要引用的全局变量. 内 ...
- 《征服 C 指针》摘录2:C变量的 作用域 和 生命周期(存储期)
在开发一些小程序的时候,也许我们并不在意作用域的必要性.可是,当你书写几万行,甚至几十万行的代码的时候,没有作用域肯定是不能忍受的. C 语言有如下 3 种作用域. 1.全局变量 在函数之外声明的变量 ...
- C语言 变量的作用域和生命周期(转)
转自 https://blog.csdn.net/u011616739/article/details/62052179 a.普通局部变量 属于某个{},在{}外部不能使用此变量,在{}内部是可以使用 ...
- C语言中变量的作用域和生命周期
变量的类型: 1. 局部变量和全局变量 局部变量也称为内部变量. 局部变量是在函数内作定义说明的.其作用域仅限于函数内, 离开该函数后再 使用这种变量是非法的. 全局变量也称为外部变量,它是在函数外部 ...
- C语言基础 (10) 变量作用域,生命周期 内存结构
01 课程回顾 1.指针数组 注意: 对于数组来说,在使用sizeof的时候a和&a[0]是不一样的, 虽然以%x打印出来他们都是地址 2.值传递 int a; fun(a); int *** ...
- spring作用、spring注解、管理对象的作用域与生命周期、自动装配、Spring的框架包有哪些作用是什么
Spring 1. 作用 创建和管理对象,使得开发过程中,可以不必使用new关键字创建对象,而是直接获取对象!并且,还可以通过一些配置,使得某些获取到的对象,其中某些属性已经是被赋值的! 2. Spr ...
- Mybatis学习-配置、作用域和生命周期
核心配置文件:Mybatis-config.xml Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息 配置(configuration) 在mybatis-config.xm ...
- MyBatis 作用域和生命周期
理解到目前为止所讨论的类的作用域和生命周期是非常重要的.如果使用不当可导致严重的并发性问题. SqlSessionFactoryBuilder 这个类可以在任何时候被实例化.使用和销毁.一旦您创造了 ...
- Bean 注解(Annotation)配置(2)- Bean作用域与生命周期回调方法配置
Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...
随机推荐
- Python神经网络编程笔记
神经元 想一想便知道,当一个人捏你一下以至于你会痛得叫起来的力度便是神经元的阈值,而我们构建的时候也是把这种现象抽象成一个函数,叫作激活函数. 而这里便是我们使用sigmoid函数的原因,它是一个很简 ...
- Ansible-基本概述
为什么要自动化运维 纯手动软件安装部署方式 我们以 10 台机器部署 Nginx 为例.部署步骤如下: 1.通过 ssh 登录一台机器: 2.yum install -y nginx 或者 获取安装包 ...
- OCR场景文本识别:文字检测+文字识别
一. 应用背景 OCR(Optical Character Recognition)文字识别技术的应用领域主要包括:证件识别.车牌识别.智慧医疗.pdf文档转换为Word.拍照识别.截图识别.网络图片 ...
- vs2019 目标框架是灰色的原因
原因一是没有安装.net core 包: 如果是桌面程序,不是web程序:不安装.net core包的情况,修改工程文件 TargetFrameworks ----> TargetFrame ...
- Ubuntu pppoe宽带拨号相关问题
因为可视化界面没有相关设置,因此采用终端命令的方法. 测试环境:Ubuntu 18.0.4 pppoe的配置:$ sudo pppoeconf 然后进入此界面进行一系列宽带拨号的设置. 联网:$ su ...
- vue+express+mysql项目总结(node项目部署阿里云通用)
原文发布于我的个人博客上:原文点这里 前面经历千辛万苦,终于把博客的所有东西都准备好了,现在就只等部署了.下面我介绍下我的部署过程: 一.购买服务器和域名 如果需要域名(不用域名通过ip也可以 ...
- 【Spring Data 系列学习】Spring Data JPA 自定义查询,分页,排序,条件查询
Spring Boot Jpa 默认提供 CURD 的方法等方法,在日常中往往时无法满足我们业务的要求,本章节通过自定义简单查询案例进行讲解. 快速上手 项目中的pom.xml.application ...
- Redis使用指南
原文链接 能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 设置过期时间.释放资源 使用Redis做K-V存储,一定要注意过期时间的把控,任何K- ...
- go源码分析(二) 使用go http包开发web时遇到的坑之重复注册Handle路由
我们使用Handle注册http时 如果添加两行,即重复注册函数. http.HandleFunc("/",index) http.HandleFunc("/" ...
- typescript package.json vscode 终端 运行任务 Ctrl shift B
{ "dependencies": { "typescript": "^3.6.4" } }