存储类

C为变量提供了5种不同的存储类型:

  • 自动
  • 寄存器
  • 具有代码块作用域的静态
  • 具有外部链接的静态
  • 具有内部链接的静态

不同角度描述变量:

  • 存储时期 变量在内存中保留的时间
  • 变量作用域(Scope)以及它的链接(Linkage) 变量的作用域和链接一起表明程序的哪些部分可以通过变量名来访问该变量

不同的存储类提供了变量的作用域、链接以及存储时期的不同组合。

作用域

作用域分为:

  • 代码块作用域(block scope)

    代码块在C中指的是一对花括号之间。定义在代码块之间的变量其可见性仅存在于其定义处和闭花括号之间。函数的形式参量、for、while、if-else等中定义的变量都是属于代码块作用域。
  • 函数原型作用域(function prototype scope)

    函数原型作用域从变量定义处一直到函数原型结尾,这解释了为什么我们平时定义函数原型的时候的形式参量名字可以与函数定义中形参名字不同,甚至根本没有名字:编译器只关心原型形参的数据类型,因为函数原型形参变量作用域极短,其名称并不重要。
    int function(int arg1, int arg2);
    int function1(int, int);

    但是有些情况下函数原型里的形参名称有作用:存在变长数组参量时。

    void function2(int n, int m, int array[m][n]);

    变长数组array中使用的变量m n是之前已经声明的。

  • 文件作用域(file scope)

    一个在所有函数之外定义的变量具有文件作用域。具有文件作用域的变量其可见性存在于其定义处到文件结尾处。这样的变量也被成为全局变量(global variable)
    int function(int arg1, int arg2);
    int file_scope = 0;
    int main() {
    return 0;
    }
    int function(int a, int b){ }

    这里面file_scope对于mainfunction两个函数都是可见的。

链接

C变量具有下列三种链接之一:

  • 外部链接 external linkage
  • 内部链接 internal linkage
  • 空链接 no linkage

具有函数原型作用域或者代码块作用域的变量具有空链接,他们是由其定义所在的代码块或者函数原型私有的。具有文件作用域的变量可能有内部或者外部链接。有外部链接的变量可以在一个多文件程序任何地方进行访问。一个具有内部链接的变量可以在其所在的单个文件里任何地方访问。

区分一个具有文件作用域是外部链接还是内部链接可以看static关键字。

static int full_global = 0;//内部链接 只在本文件中全局可见
int file_global = 1;//外部链接 full_global可以被同一程序多个文件访问

存储时期

C变量可以有以下两种存储时期之一:

  • 静态存储时期 static storage duration

    具有静态存储时期的变量会在程序执行期间一直存在。具有文件作用域的变量是具有静态存储时期的。
  • 自动存储时期 automatic storage duration

    具有代码块作用域的变量一般情况下具有自动存储时期。这样的变量在程序进入代码块时被分配内存,退出代码快时其内存被释放。

由以上作用域、链接、存储时期得到了五种存储类:

存储类 存储时期 作用域 链接 声明方式
自动 自动 代码块 代码块内
寄存器 自动 代码块 代码块内,使用关键字register
具有外部链接的静态 静态 文件 外部 所有函数之外
具有内部链接的静态 静态 文件 内部 所有函数之外,使用修饰符static
空链接的静态 静态 代码块 代码块内,使用修饰符static

自动变量

默认情况下,在代码块或函数的头部定义的任意变量都属于自动存储类型。也可以使用关键字auto来显示的表明此变量为自动变量:auto int auto_var = 0;,这样做的目的可以是显式覆盖一个外部函数定义的同名变量或者强调该变量的存储类型不可以改变为其他存储方式。auto称为存储类说明符(storage class specifier)。

自动变量具有代码块作用域和空链接,这样只有在变量定义的代码块里才可以通过变量名访问该变量。C也允许通过向函数传递参数地址来访问变量,但这样属于间接访问,不是通过变量名直接访问的。

覆盖(hide)指的是不同作用域的变量名称相同的情况下,会根据程序所在环境按名称访问相应的变量值。举个栗子:

int x = 0;
while (x++ < 3){
int x = 10;
x++;
printf("%d\n",x);
}
printf("%d",x);

输出:

11
11
11
4

while循环每次判断用的是外层x的值,进入代码块后,x被重新定义并初始化,代码块里面使用的是暂时的x,每次循环退出时x会被销毁,因此也就有了上面的输出。写程序千万不要写这种代码!!

自动变量不会被自动初始化,必须得显式初始化!但是全局变量会存在默认初始化!

寄存器变量

变量一般存储在计算机的内存中,可以通过关键字register来显式得申请将变量存储在CPU寄存器中或者存储在速度最快的可用内存来达到更快的访问和操作速度。但是,显式得声明变量为寄存器变量并不会导致该变量一定是寄存器变量,这需要编译器考虑到寄存器数目或者高速可用内存数量,如果不行,那么该变量就会变为自动变量。寄存器变量与自动变量有相同的代码块作用域、空链接、自动存储时期,但是无法使用&操作符获取寄存器变量的地址,即使没有申请成功,该变量为自动变量,也还是无法获取它的地址。

register关键字能够申请的类型是有限的,像C primer plus里提到了处理器可能没有足够大的寄存器来容纳double类型的数据。

具有代码块作用域的静态变量

静态变量并不是指变量不可变,而是指变量的位置固定不动。在代码块内部使用修饰符static声明变量会产生具有代码块作用域、空链接但静态的变量。

栗子:

#include<stdio.h>

void block_static(void);

int main(int argc,char *argv[]) {
for (int i = 0; i < 4; ++i) {
printf("loop %d\n:",i);
block_static();
} return 0;
} void block_static(void){
int test = 10;
static int foo = 100;
printf("test = %d, foo = %d\n",test++,foo++);
}

输出结果:

loop 0
:test = 10, foo = 100
loop 1
:test = 10, foo = 101
loop 2
:test = 10, foo = 102
loop 3
:test = 10, foo = 103

静态变量foo的内存位置固定不变,每次block_static函数执行时都会访问它的值,它每次增加的值并没有丢失,static int foo = 100;这个语句既在block_static第一次执行时声明了静态变量foo,之后在block_static每次执行时告诉这个函数foo对其是可见的,它知道这个变量的地址,它可以去访问。而test变量就不同,它是自动变量,每次blocl_static执行时,test先被分配内存并初始化,执行结束时内存被回收,丢失这个值,下一次函数执行时已经丢失了对原先test内存地址访问权,会重新创建test并分配内存然后销毁,周而复始。

形式参量无能使用static修饰。

具有外部链接的静态变量

具有外部链接的静态变量具有文件作用域、外部链接和静态存储时期,也被称为外部变量。

当想要创建一个外部变量时,把变量定义在所有函数之外即可,也可以显式地使用 extern关键字来声明。当变量是在别的文件定义的时,必须使用extern声明变量。

#include<stdio.h>

int Global = 100;

int main() {

    extern int Global;//这一行其实可以省略

    return 0;
}

上面的例子中main函数要访问Global变量无须声明,这样做只是为了表明main函数需要用它而已。需要注意的是如果这样写:extern int Global = 10;是会出错的,因为外部变量不允许在函数内部进行定义。变量的声明与定义是不同的。这里Global既然是外部变量,就不允许函数内部对其进行修改(具体见此处)。另外,如果把extern去掉会产生一个自动变量Global将外部变量Global覆盖掉。

外部变量可以显式地初始化,但是若是没有显式初始化,那么默认会被赋值为0。而且,只可以用常量表达式来对外部变量进行初始化

int Global = 100;
int y = 3 + 10;//合法,3+10是一个常量表达式
size_t z = sizeof(int);//合法,sizeof是编译时运算符,当其操作数不为变长数组时,返回值为一个常量
int x = y; //不合法,y是一个变量 int main() { extern int Global; return 0;
}

extern关键字

int tern = 1;//外部变量tern定义,初始化

int main(void){
extern int tern;
return 0;
}

在上面的代码段中tern有两次声明,第一次是对外部变量的定义声明为变量分配了内存空间,第二处因为有extern存在,表明是对外部变量extern的引用,为引用声明,并不涉及内存分配。在C Primer Plus中这样说:关键字extern表明该声明不是一个定义,因为它指示编译器参考其他地方。

如果这样做:

extern int tern;
int main(void){
}

那么编译器会假定tern是在其他文件中定义的外部变量,而不会引起空间分配。因此,不要用extern来进行外部定义,只用它来引用一个已经存在的外部定义

一个外部变量只允许一次初始化,必须在外部变量被定义声明的同时进行初始化。

extern int tern = 1000;不合法,因为此时编译器假定tern已经存在,这是一个引用声明,不是定义声明。不能对tern进行二次初始化。

具有内部链接的静态变量

具有内部链接的静态变量具有静态存储时期、文件作用域以及内部链接。它通过static关键字在函数外部进行定义。

这种变量不同于外部变量,它只能被同文件中的函数进行访问,在函数中同样可以使用extern关键字来进行引用声明,但不会改变它的内部链接。

int global_full = 100;//外部链接、文件作用域、静态
static int global_not_full = 10;//内部链接、文件作用域、静态 int main() { extern int global_full;
extern int global_not_full;//global_not_full仍是内部链接
//这两种其实都多余,main中可以直接访问这两个变量
return 0;
}

多文件

当一个C程序包含多个独立代码文件时,若需要共享外部变量,这时候外部链接、内部链接才显得重要。通过在一个文件中定义外部变量,在其他文件中引用声明这个变量实现共享。除了这个本身的定义声明之外,其他所有生命都必须使用关键字extern来进行引用,并且只能在定义的时候初始化一次。注意,其他文件要想使用这个变量,必须显示的使用extern声明,否则该变量不能用于其他文件。

这是我对《C Primer Plus》第十二章存储类、链接和内存管理所写的一部分笔记,未完待续。

C Primer Plus--C存储类、链接和内存管理之存储类(storage class)的更多相关文章

  1. 《C prime plus (第五版)》 ---第12章 存储类.链接和内存管理

    12-1:存储类: 1.作用域: 代码块作用域,函数原型作用域和文件作用域. 2.链接:分为外部链接,内部链接和空链接.代码块作用域和函数原型作用域都是空连接,意味着是私有的.而文件作用域的变量可能是 ...

  2. C语言中存储类别、链接与内存管理

      第12章 存储类别.链接和内存管理 通过内存管理系统指定变量的作用域和生命周期,实现对程序的控制.合理使用内存是程序设计的一个要点. 12.1 存储类别 C提供了多种不同的模型和存储类别,在内存中 ...

  3. C Primer Plus之存储类、链接和内存管理

    存储时期即生存周期——变量在内存中保留的时间 变量的作用域和链接一起表明程序的哪些部分可以通过变量名来使用该变量. 注意:生存期和作用域是两个不同的概念. 作用域    作用域描述了程序中可以访问一个 ...

  4. 【C语言学习笔记】存储类、链接和内存管理

    因为对内存管理部分一直没有很清楚的思路,所以一直在找资料想系统看一下这部分的内容.在C primer plus里看到了这一章,虽然大多都是心知肚明的东西,但是还是很多概念性系统性的东西让我眼前一亮,把 ...

  5. 存储类、链接和内存管理(c prime plus)

    首先介绍三个概念: (1)作用域:作用域描述了程序中可以访问一个标识符的一个或多个区域. 一共有三种作用域:代码块作用域.函数原型作用域和文件作用域 a.代码块作用域:一个代码块是包含在开始花括号和对 ...

  6. C++类成员在内存中的存储及对齐方式

    前言:数据对齐的基本理论参见文章:http://www.cnblogs.com/MyBlog-Richard/articles/5993448.html 一.空类的大小 C++中空类的大小是1,这是因 ...

  7. MongoDB源码概述——内存管理和存储引擎

    原文地址:http://creator.cnblogs.com/ 数据存储: 之前在介绍Journal的时候有说到为什么MongoDB会先把数据放入内存,而不是直接持久化到数据库存储文件,这与Mong ...

  8. C Primer Plus--C存储类、链接和内存管理之动态分配内存及类型限定词

    目录 存储类说明符 存储类和函数 动态分配内存 malloc函数 free函数 calloc函数 动态分配内存的缺点 C类型限定关键字 constant定义全局常量 volatile关键字 restr ...

  9. 【C语言学习】《C Primer Plus》第12章 存储类、链接和内存管理

    学习总结 1.作用域可分为代码块作用域.函数原型作用域或者文件作用域. 代码块作用域例子: { for(int i=0;i<10;i++){  //C99允许 …  //i的作用域 } ... ...

随机推荐

  1. canvas上画出坐标集合,并标记新坐标,背景支持放大缩小拖动功能

    写在前面:项目需求,用户上传一个区位的平面图片,用户可以在图片上添加新的相机位置,并且展示之前已绑定的相机坐标位置,图片支持放大缩小&拖动的功能.新增坐标,页面展示相对canvas定位,保存时 ...

  2. elementUI——主题定制

    需求: 设计三套主题色+部分图标更换: 实现方式汇总: 1.传统做法,生成多套css主题包,切换link引入路径切换href实现,参考网站:http://jui.org/: <link id=& ...

  3. zabbix4.2监控nginx

    项目环境: 操作系统 主机名 IP地址 Centos7.6 x86_64 zabbix-server 192.168.1.18 Centos7.6 x86_64 zabbix-client 192.1 ...

  4. Nginx编译安装和平滑升级

    一.Nginx的编译安装 1.安装依赖包gcc,gcc-c++,pcre,openssl-devel 命令:yum -y install gcc gcc-c++ pcre-devel openssl- ...

  5. Apache实验-目录别名

    一.作用介绍 在一些情况下,我们的资源文件都在非/var/www/html目录下,例如/var/www/html/sohu.这样的话我们在输入网址的时候就需要在网站根目录下再输入完整的目录.所以我们可 ...

  6. h5中history实例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. java加密算法-MD5

    import java.security.MessageDigest; public class MD5Util { /*** * MD5加密 生成32位md5码 * @param 待加密字符串 * ...

  8. 【转】使用JavaParser获得Java代码中的类名、方法形参列表中的参数名以及统计总的文件个数与不能解析的文件个数

    遍历目录查找Java文件: public static void ergodicDir(File dir, HashSet<String> argNameSet, HashSet<S ...

  9. P1903 [国家集训队]数颜色 / 维护队列(带修莫队)

    题目描述: 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. ...

  10. linux系统编程之信号(五)

    今天继续对信号进行学习,开始正入正题: sigaction函数: 安装信号之前我们已经学过一个函数:signal,它最早是在unix上出现的,它是对不可靠信号进行安装的,之后出现了可靠信号和实时信号, ...