今天阅读了《C和指针》有关“数据”那一章节的内容,主要梳理一下作用域、链接属性、存储类型(C primer plus中为存储时期)。存储类是《C Primer Plus》中提出的概念,共有5种可能的类型:
这五种存储类是由存储时期(存储类型)、作用域、链接属性这三个属性共同决定的,这三个属性相互关联但又不尽相同,我们来一 一分析。
作用域
作用域有4种——文件作用域、函数作用域、代码块作用域和原型作用域。
文件作用域:在任何代码块之外声明的标识符都具有文件作用域,拥有文件作用域的变量从它的声明之处到文件结尾都是可以访问的。
代码块作用域:位于一对花括号之间的所有语句称为一个代码块,任何在代码块的开始位置声明的标识符都具有代码块作用域。
函数作用域:它只适用于语句标签,语句标签用于goto语句。
原型作用域:只适用于在函数原型中声明的参数名。
例:
int a;
int fun2(int f);
int main()
{
int b=10;
int e = fun1(b);
return 0;
}
int fun1(int c){
int d=c;
return d;
}
其中a fun2 fun1 具有文件作用域,b c d e具有代码块作用域,f具有原型作用域。
你可能对原型作用域不太理解,假如有这样一个函数原型
void fun (int m,int n,int array[m][n]);
此时m和n的作用域就有了意义——作为array数组的参数,m n 就具有原型作用域。
链接属性
当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行文件,如果相同的标识符出现在几个不同的源文件时,它们是表示同一个实体还是表示不同的实体?这就要看这些标识符的链接属性了。
链接属性有三种——extern(外部)、internal(内部)和none(无)。
none链接属性的标识符总是被当做独立不同的实体,例如函数中声明的局部变量。
internal链接属性在该文件内有效,即在该文件内所有相同的标识符都是指一个实体,不同的文件内所指的实体不同,例如在main函数外一个用static 关键字修饰的变量。
external链接属性在不同的文件内都是指同一个实体,例如在main函数中不用static关键字修饰的变量。
例子:
typedef char*a;
int b;
int c(int d)
{
int e;
int f(int g);
...
}
b c f 的链接属性都为extern,其余都为none。
在上例中,若将int b改为static int b,则b的链接属性被修改为internal,这种效果只对原先就具有extern链接属性的变量有用。若将int e改为static int e,则不会改变它的链接属性,改变的是它的存储时期(存储类型)。
存储时期(存储类型)
存储时期有两类——静态存储时期和自动存储时期。
变量的存储时间决定变量何时创建、何时销毁以及它的值将保持多久。
有三个地方可以存储变量:普通内存、运行时堆栈、硬件寄存器。
具有静态存储时期的变量会被存放在普通内存,具有自动存储时期的变量放在运行时堆栈或者硬件寄存器。
静态存储时期的变量一般用static关键字修饰,自动存储时期的变量使用auto关键字修饰(缺省时默认是auto类型),对应于自动存储时期的变量还可以用register来修饰,这个关键字的意思是建议机器把该变量存放在硬件寄存器中进行处理(因为放在寄存器比放在内存要处理得块快),但机器到底采不采用你的建议就要根据具体的情况了。
对于静态存储时期的变量,程序在编译的时候就会在内存中开辟一块空间来专门存放它,如果你对它进行了初始化,那么这个值会被存放在相应的内存空间,如果你没有初始化,程序会默认将它初始化为0。
对于自动存储时期的变量,程序不会对它进行初始化,因为它在运行时是存在于运行时堆栈中的,调用它的时候它被创建并使用,调用完毕之后就会将它销毁,倘若再次调用它时,它很有可能已经不在之前的位置了,那对它进行初始化就是无谓的操作。
小结一下,作用域指明了标识符所作用的范围,最大也就是文件作用域;链接属性指明了标识符在编译连接时的合并与隔离;存储时期指明了标识符的存放位置和生命周期。这三种属性的集合定义了变量的整体属性。
这里需要注意的static关键字的用法,
若static作用于一个extern链接属性的标识符,则会将该标识符的链接属性修改为internal。比如在文件A中定义了函数function:
static void function(int a);则该函数就变为文件A私有,不会被其他的文件所访问到。
若static作用于一个auto存储时期的标识符,则该标识符的存储时期就会被改为静态存储时期。
有人可能会想,既然可以用extern属性来扩大标识符的链接属性,那多多应用extern不就很方便吗?然而在实际开发中应尽量少地使用这种方法,因为这会破坏模块与模块之间的封装性,因为往往会由于修改了一个全局变量而要去对多个涉及到该变量的模块进行变动。