C Primer Plus--C存储类、链接和内存管理之存储类(storage class)
存储类
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)的更多相关文章
- 《C prime plus (第五版)》 ---第12章 存储类.链接和内存管理
12-1:存储类: 1.作用域: 代码块作用域,函数原型作用域和文件作用域. 2.链接:分为外部链接,内部链接和空链接.代码块作用域和函数原型作用域都是空连接,意味着是私有的.而文件作用域的变量可能是 ...
- C语言中存储类别、链接与内存管理
第12章 存储类别.链接和内存管理 通过内存管理系统指定变量的作用域和生命周期,实现对程序的控制.合理使用内存是程序设计的一个要点. 12.1 存储类别 C提供了多种不同的模型和存储类别,在内存中 ...
- C Primer Plus之存储类、链接和内存管理
存储时期即生存周期——变量在内存中保留的时间 变量的作用域和链接一起表明程序的哪些部分可以通过变量名来使用该变量. 注意:生存期和作用域是两个不同的概念. 作用域 作用域描述了程序中可以访问一个 ...
- 【C语言学习笔记】存储类、链接和内存管理
因为对内存管理部分一直没有很清楚的思路,所以一直在找资料想系统看一下这部分的内容.在C primer plus里看到了这一章,虽然大多都是心知肚明的东西,但是还是很多概念性系统性的东西让我眼前一亮,把 ...
- 存储类、链接和内存管理(c prime plus)
首先介绍三个概念: (1)作用域:作用域描述了程序中可以访问一个标识符的一个或多个区域. 一共有三种作用域:代码块作用域.函数原型作用域和文件作用域 a.代码块作用域:一个代码块是包含在开始花括号和对 ...
- C++类成员在内存中的存储及对齐方式
前言:数据对齐的基本理论参见文章:http://www.cnblogs.com/MyBlog-Richard/articles/5993448.html 一.空类的大小 C++中空类的大小是1,这是因 ...
- MongoDB源码概述——内存管理和存储引擎
原文地址:http://creator.cnblogs.com/ 数据存储: 之前在介绍Journal的时候有说到为什么MongoDB会先把数据放入内存,而不是直接持久化到数据库存储文件,这与Mong ...
- C Primer Plus--C存储类、链接和内存管理之动态分配内存及类型限定词
目录 存储类说明符 存储类和函数 动态分配内存 malloc函数 free函数 calloc函数 动态分配内存的缺点 C类型限定关键字 constant定义全局常量 volatile关键字 restr ...
- 【C语言学习】《C Primer Plus》第12章 存储类、链接和内存管理
学习总结 1.作用域可分为代码块作用域.函数原型作用域或者文件作用域. 代码块作用域例子: { for(int i=0;i<10;i++){ //C99允许 … //i的作用域 } ... ...
随机推荐
- 设置 SQL*Plus 的运行环境
SQL*Plus 的运行环境是用来输入.执行 SQL*Plus 命令和显示返回结果的场所,设置合适的 SQL*Plus 运行环境,可以使 SQL*Plus 按照用户的要求运行和执行各种操作.set 命 ...
- maven cmd 命令
1. mvn clean install :重新清理打包 2.详见:https://www.cnblogs.com/lukelook/p/11298168.html mvn versions:upd ...
- SELinux 权限设置
SELinux 权限设置 一.SELinux简介 SELinux全称是Security Enhanced Linux,由美国国家安全部(National Security Agency)领导开发的GP ...
- DTD学习
DTD 简介 文档类型定义(DTD)可定义合法的XML文档构建模块.它使用一系列合法的元素来定义文档的结构.DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用. XML文件内部引用: 外部 ...
- 传入json字符串的post请求
/** * 传入json字符串的post请求 * @Title: getRequsetData * @Description: TODO * @param @param url * @param @p ...
- server端和前端的区别
1.服务稳定性 server端可能会遭受各种恶意攻击和误操作 单个客户端可以意外挂掉,但是服务端不能 node中用pm2做进程守候,一旦挂掉,自己会重启 2.考虑内存和cpu(优化,扩展) 客户端独占 ...
- 云计算/云存储---Ceph和Openstack的cinder模块对接方法
1.创建存储池 在ceph节点中执行如下语句. #ceph osd pool create volumes 2.配置 OPENSTACK 的 CEPH 客户端 在ceph节点两次执行如下语句,两次{y ...
- python安装脚本
[root@dn3 hadoop]# cat install.py #!/usr/bin/python #coding=utf- import os import sys : pass else: p ...
- webpack loader和插件的编写原理
webpack自定义loader和插件的api网址:https://www.webpackjs.com/api/loaders/ 点击顶部API,看左侧api: 1. 如何编写一个loader 实现的 ...
- Two Year's Harvest
转眼间来到这里已经两年,在懵懵懂懂中渐渐在成长,一步一步走过脚下的路.这两年你说长,时间也是不短,但说长吧,时间又匆匆在指间匆匆流走.还记得大一时老师为我们讲专业课,那时候还不知道TGB,只是在五月的 ...