存储类

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对于main和function两个函数都是可见的。

链接

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. python PIL图像处理库

    1. Introduction PIL(Python Image Library)是python的第三方图像处理库,但是由于其强大的功能与众多的使用人数,几乎已经被认为是python官方图像处理库了. ...

  2. Android里的Dalvik、ART、JIT、AOT有什么关系?

    JIT,Just-in-time,即时编译,边运行边编译: AOT,Ahead Of Time,提前编译,指运行前编译. 区别 这两种编译方式的主要区别在于是否在“运行时”进行编译 优劣JIT优点: ...

  3. 大学课后答案微信小程序项目实践(1)

    叨逼叨 还记得以前小编上大学那会苦于课后习题没有答案...到了考试....就像下面这个图一样- 现在,那些同样在纠结于书本后的答案太遥远的同学们,要告诉你们一个好消息,个人历时两周作业的时间开发的小程 ...

  4. Redis 学习-持久化与主从复制

    一.持久化 1. RDB rdb 是 redis 内存到硬盘的快照,用于持久化 ①. 通过执行命令,主动保存快照 save # 执行保存快照,执行时 redis 会处理阻塞状态直至执行完成. bgsa ...

  5. 卷积神经网络快速入门【基于TensorFlow】

    一.概述 卷积神经网络[Convolutional neural networks]里面最重要的构建单元是卷积层.神经元在第一个卷积层不是连接输入图片的每一个像素,只是连接它们感受野1的像素,以此类推 ...

  6. DRF 有无外键操作实例

    models.py from django.db import models # Create your models here. class Category(models.Model): &quo ...

  7. linux 显示不可见字符

    在Linux中,cat -A file可以把文件中的所有可见的和不可见的字符都显示出来,在Vim中,如何将不可见字符也显示出来呢?当然,如果只是想在Vim中查看的话,可以这样:%!cat -A在Vim ...

  8. 190919 centos系统中python2卸载重装

    问题:某些原因卸载了python2,连带卸载了yum工具. 解决思路: 如果服务器没有什么东西,重装系统最省事.但是如果不允许重装,那就只能按部就班的恢复python2和yum. 步骤: 删除pyth ...

  9. k8s 笔记

    一. 解决pod间依赖性 1.手动的采用不同顺序启动不同pod 2.定义restart policy(默认为alway,我们可以定义当某条件不满足时就一直重启,当满足条件是才启动容器) 3.如果对于强 ...

  10. Celebrate it, this is my first time on this blog.

    After hovered around the technology edge many years(so ashamed), I want to collected all the points ...