在C语言中,一个数据对象(往往是指变量)可以由3种属性来描述:作用域,存储时期,链接。每种属性有不同的值,这些不同值的组合构成数据对象的存储模型,又称为存储类,即,一个数据对象如何存在于计算机中。以下描述中,我们以变量为例(另一个例子是函数)代表数据对象。
一、作用域
作用域属性描述了一个数据对象可以在(源代码的)哪些区域被访问,它有以下几个值:代码块、函数原型、文件。
1、文件作用域
函数是C源代码文件的基本组织单位,但是如果一个变量定义在所有函数之外,即没有在任何一个函数体内,那么该变量就具有文件作用域,其范围是从变量定义处直到文件结尾。在此范围内的任何一个地方都可以访问它。需要注意的是,C语言规定所有变量都必须先声明再使用,所有在定义变量之前的地方是不能访问该变量的。
2、函数原型作用域
首先应弄清楚什么是函数原型。有关函数的几个概念很容易混淆,它们是:函数原型、函数定义、函数声明。函数声明包括前两者,而函数原型和函数定义的区别在于有没有函数体,有就称为定义,没有就是原型。更本质的区别在于,函数定义是要分配存储空间的,而函数原型不必。
函数原型的作用域是从变量定义处直到原型声明的末尾,但是这个作用域似乎没啥用啊。。。
3、代码块作用域
代码块是指包含在一对花括号之间的区域,代码块作用域的范围也就是这对花括号之间。
void show(int i) {
int j;
if (i < 0) {
int k = 2;
i = i + k;
}
j = i * 3;
return j;
}
在这段代码中,k定义在if{}块中,所以在该块之外k是无法访问的,比如把j = i * 3;换成j = i * k;就是错的。
要注意的是,这段代码是一个函数定义,在定义中有形式参数i,它的作用域也是代码块,即show{}的函数体内,而不是函数原型中的那样。也许这就是为什么把函数原型作用域单独提出来的原因吧。。。
C99把代码块的概念扩大到for() while() if()语句的圆括号,在圆括号内定义的变量也具有代码块作用域。
二、存储时期
该属性描述了一个变量的“存活时间”,它有两个值:静态存储时期和自动存储时期。前者在程序执行期间一直存在;后者在使用完毕之后就被系统自动清除,释放内存。
存储时期和作用域是相互关联的,不能随意组合。一般来说,具有文件作用域的变量都有静态存储时期;而代码块作用域变量具有自动存储时期。
三、链接
链接属性描述了一个变量是否可以被其他文件引用。一个C程序往往由多个C源文件组成,所以在一个文件中定义的变量如果可以在另一个文件中被引用会方便很多。但是也有一些变量是专门为本文件服务的,不希望被其他文件引用,否则会引起混乱。链接属性就是为解决这一问题设计的。
该属性有3个值:内部链接、外部链接和空链接。链接同样和作用域紧密相关,不能随意组合。一般来讲,具有文件作用域的变量可能具有内部链接或外部链接,而具有代码块作用域或函数原型作用域的变量只有空链接。具有外部连接的变量可以被其他文件使用;具有内部链接的变量只能在本文件内部使用;而具有空链接的变量只能在代码块或函数原型内使用。
四、组合
三种属性经过“有条件的”组合之后形成以下五种存储类:自动、寄存器、具有外链的静态、具有内链的静态、具有空链的静态。用下图说明它们的各个属性组合:
5种存储类按照作用域来划分为代码块和文件两大类,作用域确定了,其他的都好说。
存储时期的自动与静态可以分别用关键字auto和static来显式声明,而链接的内部或外部分别由static和extern来修饰。但是对于不同作用域的变量,存在着默认情况。
由上表可知,只要是作用域为代码块的变量,其存储时期默认为自动,从而auto都是省略不写的;但是要使这样的变量具有静态存储时期,就必须显式的使用static声明。链接类型的话,都是空链接,不再区别内外。也可以看到,这类变量都是定义在代码块内部的,抛开寄存器变量这个异类,这类变量之间的唯一区别就在于有没有被static修饰(从而意味着是不是静态变量)。
另一大类变量就是定义在所有函数之外的具有文件作用域的变量了,它们的存储时期只能是静态的,不可以被auto修饰;而链接类型默认为外链,于是extern一般都省略不写。要使之成为内链,则需使用static修饰。
可以总结一下static的作用:显然它对不同作用域的变量的作用是不同的,对代码块作用域的变量,它的作用是使之成为静态变量;而对于文件作用域的变量,它的作用是使之成为内链变量。
把变量划分这么细有啥用呢?比如,在文件开头定义了一个变量a,然后在函数内部又定义了一个同名变量,那么计算机会不会混淆呢?在函数内部到底谁在起作用呢?这时,存储类就派上用场了:不同的存储类变量会被存放在内存的不同区域,即使它们是重名的。在函数内部,内部定义的变量将覆盖外部变量,起作用的将是内部变量。
还有,就是两类变量的初始化方案不同:文件作用域变量如果没有初始化,系统会自动为其赋一个默认值,但是代码块作用域的变量享受不到这个待遇。