C语言中存储类别、链接与内存管理
第12章 存储类别、链接和内存管理
通过内存管理系统指定变量的作用域和生命周期,实现对程序的控制。合理使用内存是程序设计的一个要点。
12.1 存储类别
C提供了多种不同的模型和存储类别,在内存中储存数据。
被储存的每一个值都占用一定的物理内存;C语言把这样一块内存称为对象(object)。
对象可以储存一个或多个值。或者并未存储实际的值。
C不是面向对象的语言,面向对象编程中的对象指的是类对象。
程序需要一种方法访问对象 ---> 通过声明变量来完成
int entity =3;
该声明创建了一个名为entity的标识符(identifier)。标识符是一个名称。
标识符可以用来指定特定对象的内容。标识符遵循变量的命名规则。
标识符是软件指定硬件内存中的对象的方式。
变量名(标识符)不是唯一指定对象的途径。
int * pt = &entity;
*pt不是一个标识符,它不是一个名称。它确实指定了一个对象。
左值:指定对象的表达式;*pt 和entity都是左值。
再举一个例子:const char * pc = “Behold a string literal!”
const限定符保证被pc指向的内容不被修改。但是不能保证pc不指向别的字符串;
存储期(storage duration)描述对象。存储期是指对象在内存中保留了多长时间。
标识符(identifier)用于访问对象。
可用作用域(scope)和链接(linkage)描述标识符。
标识符的作用域和链接表明了程序的哪些部分可以使用它。
不同的存储类别具有不同的存储期、作用域和链接。
标识符可以再源代码的多文件中共享,可用于特定文件的任意函数中,可仅限于特定函数中使用。
对象可存在于程序的执行期,也可以仅存在于它所在函数的执行期。对于并发编程,对象可以再特定线程的执行期存在。可以通过函数调用的方式显式分配和释放内存。
12.1.1 作用域
块:是用花括号括起来的代码区域。整个函数体是一个块,函数中的任意复合语句也是一个块。定义在块中的变量具有块作用域。
1、块作用域:(block scope)
块作用域中的变量可见范围是从定义处到包含该定义的块的末尾。注意:形式参数声明在函数花括号之前,但是它们也具有块作用域,也是属于函数这个块。C99把块的概念扩展到了for循环、while循环、do while循环和if语句所控制的代码,即是这些代码没有被花括号括起来,也算是块的一部分。一旦离开块,就不能再被访问。
2、函数作用域:(function scope)
3、函数原型作用域:(function prototype scope)
函数原型作用域的范围是从形参定义处到原型声明结束。编译器在处理该函数原型时不关心形参名,只关心它的类型。即使有形参名,也不必和函数定义中匹配。
只有在变长数组中,形参名才有用:void use_a_VLA(int n,int m, ar[n][m]);
方括号中必须使用在函数原型中已声明的名称。
4、文件作用域:(file scope)
变量定义在函数的外面,具有文件作用域。具有文件作用域的变量,从它的定义到该定义所在文件的末尾均可见。文件作用域变量也称为全局变量。
注意:翻译单元和文件
每个翻译单元均对应一个源代码文件和它所包含的文件。
源代码(.c)中包含一个或多个头文件(.h扩展名)。
C预处理实际上是用包含的头文件的内容替换#include指令。
编译器源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元(translation unit)。描述一个具有文件作用域的变量时,它的实际可见范围是翻译单元。
12.1.2 链接
C变量具有3种链接属性:外部链接、内部链接或无链接。
具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。意味着这些变量只属于定义它们的块、函数或原型私有。
具有文件作用域的变量可以是外部链接或内部链接。
外部链接变量可以再多文件程序中使用,内部链接变量智能在一个翻译单元中使用。
非正式术语:
“内部链接的文件作用域”描述仅限于一个翻译单元。->简化称呼,叫作“文件作用域”;
“外部链接的文件作用域”描述可延伸至其他翻译单元的作用域。->简化称呼,叫作“程序作用域”。
如何知道文件作用域变量是外部链接还是内部链接?可以查看外部定义中是否使用了存储类别说明符。
12.1.3 存储期
作用域和链接描述了标识符的可见性。
存储期描述了通过这些标识符访问的对象的生存期。
C对象有4个存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
如果对象具有静态存储期,那么它在程序的执行期间一直存在。
文件作用域变量:默认具有静态存储期。
对于文件作用域变量,关键字static表明了其内部链接属性,而非存储期。
以static声明的文件作用域变量具有内部链接。
所有的文件作用域变量都具有静态存储期。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存。当退出这些块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。自动变量离开函数时会被销毁。
变长数组稍有不同,它们的存储期从声明处到块的末尾。而不是块的开始和块的末尾。
块作用域的变量也可以声明为静态变量。这样可以方便其他函数通过间接的方式访问该变量,例如通过指针形参或返回值。
12.1.4 自动变量
默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。
如果为了更清楚表达你的意图,可以显式使用auto关键字。
但是注意C++中auto关键字的用法完全不同。如果写C/C++兼容的程序,最好不要用auto作为存储类别说明符。
块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量(参数用于传递变量的值和地址给另一个函数,这是间接的方法)。另一个函数可以使用同名变量,但是该变量是储存在不同内存位置上的另一个变量。
如果内层块中声明的变量与外层块中的变量同名会怎样?内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用又回到了原来的作用域。
1、没有花括号的块
作为循环或if语句的一部分,即使不使用花括号({}),也是一个块。更完整地说,整个个循环是它所在块的子块。循环体是整个循环块的子块。与此类似,if语句是一个块,与之相关联的子语句是if语句的子块。
2、自动变量的初始化
自动变量不会初始化,除非显式初始化它。
如果没有初始化,别指望整个值是0;
可以用非常量表达式,初始化自动变量,前提是所用的变量已在前面定义过。
例如:
int main(void)
{
int ruth = 1;
int rance = 5* ruth;
}
12.1.5 寄存器变量
变量储存在计算机内存中。
如果幸运的话,寄存器变量储存在CPU的寄存器中。或者概括地说,储存在最快的可用内存中。
与普通变量相比,访问和处理这些变量的速度更快。由于寄存器变量储存在寄存器而非内存中,所以无法得到寄存器变量的地址。绝大多数方面,寄存器变量和自动变量都一样,也就是说它们都是块作用域、无链接和自动存储期。
使用存储类别说明符:register便可声明寄存器变量;
int main(void)
{
register int quick;
}
“如果幸运”是因为声明变量register类别与直接命令相比更像是一种请求。请求成功就成了寄存器变量,不成功就变成普通的自动变量。编译器会根据寄存器或最快可用内存的数量衡量这个请求。即便是这样,仍然不能对该变量使用求址运算符。
可声明为register的数据类型有限,例如处理器中的寄存器可能没有足够大的空间来储存double类型的值。
12.1.6 块作用域的静态变量
静态变量(static variable):意思是该变量在内存中原地不动,并不是说它的值不变。静态可以是指它的生存期很长,跟程序一样。
如果是创建静态存储期、块作用域的局部变量,这些变量与自动变量一样,具有相同的作用域。但是程序离开它们所在的函数后,这些变量不会消失。
这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次调用之间会记录它们的值。
注意不能在函数的形参中使用static;
注意 static int stay =1; ---> 函数中有这么一条语句的话,只在编译时被初始化一次。下次再调用函数时,不会再执行这条语句。
块作用域的静态变量和文件作用域的静态变量不一样;static关键字表达的含义不同;
12.1.7 外部链接的静态变量
外部链接的静态变量具有文件作用域、外部链接和静态存储期。
该类别有时被称为外部存储类别(external storage class)。
属于该类别的变量称为外部变量(external variable)。
如果一个文件使用的外部变量定义在另一个源文件中。则必须用extern在该文件中声明该变量。
extern char Coal;
如果省略了extern关键字,相当于创建了自动变量:char Coal;
1、初始化外部变量
外部变量和自动变量类似,也可以被显式初始化。
但是如果未初始化外部变量,它们会被自动初始化为0。
这点与自动变量不同,自动变量如果没有初始化,其值是随机的。
而且注意:只能使用常量表达式初始化外部变量,这点与自动变量不同。
2、使用外部变量
int main()
{
extern int units; //这么做是为了指出该函数要使用这个外部变量。这是一个引用式声明。
}
3、外部名称
4、定义和声明
定义式声明和引用式声明是不一样的。关键字extern表明该声明不是定义。因为它指示编译器去别处查询其定义。所以定义式声明会引发内存分配存储空间。而引用式声明会指示编译器去寻找定义。
12.1.8 内部链接的静态变量
该存储类别的变量具有:静态存储期、文件作用域、内部链接;
在所有函数外部(这点与外部变量相同);
用于存储类别说明符(static)定义的变量具有这种存储类别。
例如:
static int svil = 1; //静态变量,内部链接
int main(void)
{
…
}
12.1.9 多文件
只有文件由多个翻译单元组成时,才能体现内部链接和外部链接的重要性。
复杂的C语言程序通常由多个单独的源代码文件组成。有些时候,这些文件需要共享一个外部变量。C语言通过在一个文件进行定义式声明。然后在其他文件通过引用式声明来实现共享。也就是说,除了一个定义式声明外,其他声明都要使用extern关键字。而且,只有定义式声明才能初始化变量。
外部变量定义在一个文件中,其他变量在使用它之前必须先声明它。也就是说,在某文件中对外部变量进行定义式声明知识单方面允许其他文件使用该变量,其他文件在用extern关键字声明之前不能直接使用它。
12.1.10 存储类别说明符
关键字extern和static的含义取决于上下文;
C语言中有6个关键字:auto、register、static、extern、_Thread_local、typedef
auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于块中声明的变量本身就默认具有自动存储期。所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
register说明符也只用于块作用域的变量。把变量归为寄存器的存储类别。请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。
static说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失。如果static用于文件作用域声明,作用域受限于该文件。如果static用于块作用域声明,作用域则受限于该块。
extern说明符表明声明的变量定义在别处。如果包含extern的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含extern的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这取决于该变量的定义式声明。
12.1.11 存储类别和函数
函数也有存储类别;
可以是外部函数(默认)或静态函数;
C99还新增了第3种类别,内联函数;
外部函数可以被其他文件的函数访问,静态函数只能用于其定义所在的文件。
static说明符所创建的函数,属于特定模块私有。
用extern关键字声明定义在其他文件中的函数。
除非使用static关键字,否则一般函数声明都默认为extern;
12.1.12 存储类别的选择
随意使用外部存储类别的变量导致的后果远远超过了它所带来的便利。
const数据,它们在初始化之后就不会被修改,所以不用担心它们被意外修改。
保护性程序设计的黄金法则是:“按需知道”原则。
尽量在函数内部解决该函数的任务,只共享那些需要共享的变量。
除自动存储类别外,其他存储类别也很有用。不过,在使用其他存储类别之前要考虑一下是否有必要这样做。
C语言中存储类别、链接与内存管理的更多相关文章
- C语言中存储类别又分为四类:自动(auto)、静态(static)、寄存器的(register)和外部的(extern)。
除法运算中注意: 如果相除的两个数都是整数的话,则结果也为整数,小数部分省略,如8/3 = 2:而两数中有一个为小数,结果则为小数,如:9.0/2 = 4.500000. 取余运算中注意: 该运算只适 ...
- C语言的存储类别和动态内存分配
存储类别分三大类: 静态存储类别 自动存储类别 动态分配内存 变量.对象--->内存管理 内存考虑效率(时间更短.空间更小) 作用域 链接.---->空间 存储器 ----->时 ...
- 《C prime plus (第五版)》 ---第12章 存储类.链接和内存管理
12-1:存储类: 1.作用域: 代码块作用域,函数原型作用域和文件作用域. 2.链接:分为外部链接,内部链接和空链接.代码块作用域和函数原型作用域都是空连接,意味着是私有的.而文件作用域的变量可能是 ...
- C语言中储存类别和内存管理
C语言中储存类别和内存管理 储存类别 C语言提供了多种储存类别供我们使用,并且对应的有对应的内存管理策略,在了解C中的储存类型前,我们先了解一下与储存类型相关的一些概念. 1. 基础概念 对象:不同于 ...
- C Primer Plus--C存储类、链接和内存管理之存储类(storage class)
目录 存储类 作用域 链接 存储时期 自动变量 寄存器变量 具有代码块作用域的静态变量 具有外部链接的静态变量 extern关键字 具有内部链接的静态变量 多文件 存储类 C为变量提供了5种不同的存储 ...
- C语言中存储多个字符串的两种方式
C语言中存储多个字符串的两种方式 方式一 二维字符串数组 声明: char name[][] = { "Justinian", "Momo", " ...
- C Primer Plus学习笔记(十一)- 存储类别、链接和内存管理
存储类别 从硬件方面来看,被储存的每个值都占用一定的物理内存,C 语言把这样的一块内存称为对象(object) 对象可以储存一个或多个值.一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相 ...
- 【C语言学习笔记】存储类、链接和内存管理
因为对内存管理部分一直没有很清楚的思路,所以一直在找资料想系统看一下这部分的内容.在C primer plus里看到了这一章,虽然大多都是心知肚明的东西,但是还是很多概念性系统性的东西让我眼前一亮,把 ...
- C中存储类、链接和内存管理
在C语言中,有5种不同的存储类型,即存储类.在介绍存储类之前,需要首先简单介绍几个术语.如下: 作用域:描述程序中可以访问一个标识符的一个或多个区域.一个C变量的作用域可以是代码块作用域.函数原型作用 ...
随机推荐
- LAMP 2.2 Apache配置静态缓存
这里的静态文件指的是图片.js.css 等文件,用户访问一个站点,其实大多数元素都是图片.js.css 等,这些静态文件其实是会被客户端的浏览器缓存到本地电脑上的,目的就是为了下次再请求时不再去服务器 ...
- C语言学习笔记--数组参数和指针参数
1. 数组参数退化为指针的意义 (1)C 语言中只会以值拷贝的方式传递参数,当向函数传递数组时,将整个数组拷贝一份传入函数导致执行效率低下,C 语言以高效作是最初的设计目标,所以这种方法是不可取的. ...
- 斐波那契数列-java实现
1,1,2,3,5,8,13,21...... 以上的数列叫斐波那契数列,今天的面试第一题,输出前50个,这里记录下. 方式一 package com.geenk.demo.my; /** * @au ...
- day17-jdbc 5.url介绍
url用于标识数据库的位置,用于标识找哪个数据库. 总结:url是路径,其实就是确定是哪个数据库.用来确定我用的是哪一个数据库,并且通知我这个Connection或者是这个DriverManager获 ...
- [poj2976]Dropping tests(01分数规划,转化为二分解决或Dinkelbach算法)
题意:有n场考试,给出每场答对的题数a和这场一共有几道题b,求去掉k场考试后,公式.的最大值 解题关键:01分数规划,double类型二分的写法(poj崩溃,未提交) 或者r-l<=1e-3(右 ...
- C++结构体的定义、初始化和引用
定义: 结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构. 声明一个结构体类型的形式是: struct Student{ //声明一个结构体类型Student in ...
- OpenGL编程
一.简介 OpenGL™ 是行业领域中最为广泛接纳的 2D/3D 图形 API, 其自诞生至今已催生了各种计算机平台及设备上的数千优秀应用程序.OpenGL™ 是独立于视窗操作系统或其它操作系统的,亦 ...
- Luogu 4867 Gty的二逼妹子序列
BZOJ3809,是权限题. 我永远喜欢莫队. 先莫队一下移下左右指针,然后用一个数据结构维护一下区间$[a, b]$中的颜色的值,跟着指针移动一起修改修改,每一次$query$的时候就相当于查询一下 ...
- Java分层概念(转)
Java分层概念(转) 对于分层的概念,似乎之间简单的三层,多了,就有点难以区分了,所以收藏了这个. ervice是业务层 action层即作为控制器 DAO (Data Access Object) ...
- Sql Server将一列字段拼接成字符串方法
最近在项目中遇到个问题,需要将表中某列字段合并成字符串输出,如果直接通过代码全部读取出来,再遍历进行拼接显然不是最好的方法,所以想着能否在数据读取的时候直接拼接好返回,网上搜了可通过for xml来实 ...