存储类、链接和内存管理
1.作用域
作用域描述了程序中可以访问一个标识符的一个或多个区域。
一个C变量的作用域可以是代码块作用域、函数原型作用域或者文件作用域。
代码块作用域
一个代码块是包含在开始花括号和对应的结束花括号之内的一段代码。
例如,整个函数体是一个代码块。
一个函数内的任一复合语句也是代码块。
在代码快中定义的变量具有代码块作用域(block scope),从该变量被定义的地方到包含该定义的代码块的末尾该变量均可见
。
另外,函数的形式参数尽管在函数的开始花括号前进行定义,也同样具有代码块作用域,并且属于包含函数体的代码块。
{
double patrick = 0.0 ;
return patrick ;
}
在上面这个例子中,cleo和patrick都有知道结束花括号的代码作用域。
在一个内部代码块中声明的变量,其作用域只局限于该代码块:
{
double patrick = 0.0 ;
int i ;
for ( i = 0 ; i < 10 ; i ++ )
{
double q = cleo * i ; // q作用域的开始
……
patrick *= q ;
} //q作用域的结束
return patrick ;
}
在这个例子中,q的作用域被限制在内部代码块内,只有该代码内的代码可以访问q。
C99允许在一个代码块中的任何位置声明变量,使用之前请注意编译器能不能支持。
如,你可以这样声明
{
printf ( " A C99 feature: i=%d " , i ) ;
}
2.函数原型作用域
从变量定义处一直到原型声明的末尾,这意味着编译器在处理一个函数原型的参数时,它所关心的只是该参数的类型;您使
用的名字是无关紧要的。
3.文件作用域
一个在所有函数之外定义的变量具有文件作用域 (file scope)。
具有文件作用域的便狼从它定义处到包含该定义的文件结尾处都是可见的。
int units = 0 ; //具有文件作用域的变量
void critic ( void ) ;
int main ( void )
{
……
}
void critic ( void )
{
……
}
这里,变量units具有文件作用域,在main ()和critic ()中都可以使用它。因为它们可以在不止一个函数中使用,文件作用域变量也被称为全局变量(global varibale)。
4.goto
函数作用域只适用于goto语句使用的标签。函数作用域意味着一个特定函数中的goto标签对该函数中任何地方的代码都是可见的,无论该标签出现在哪一个代码块中。
2.链接
一个C变量具有下列链接之一:
外部链接 (external linkage)
内部链接 (internal linkage)
空链接 (no linkage)
具有代码块作用域或者函数原型作用域的变量具有空链接,意味着它们是由其定义所在的代码块或函数原型所私有的。
具有文件作用域的变量可能有内部或者外部链接。
一个具有外部链接的变量可以在一个多文件程序的任何地方使用。
一个具有内部链接的变量可以在一个文件的任何地方使用。
那怎样知道一个文件作用域变量具有内部链接还是外部链接?
你可以看看在外部定义中是否使用了存储类说明符static:
static int dodgers = 3 ; //文件作用域,内部链接
int main ()
{
……
}
和该文件属于同一程序的其他文件可以使用变量giants。
变量dodgers是该文件私有的,但是可以被该文件中的任一函数使用。
2.存储时期
一个C变量有两种存储时期之一:
静态存储时期(static storage duration)和自动存储时期(automatic storage duration)。
如果一个变量具有静态存储时期,它在程序执行期间将一直存在。
具有文件作用域的变量具有静态存储时期。
注意对于具有文件作用域的变量,关键字static表示链接类型,并非存储时期。
因为所有的文件作用域变量,无论它具有内部链接,还是具有外部链接,都具有静态存储时期。
具有代码块作用域的变量一般情况下具有自动存储时期。
当程序进入定义这些变量的代码块时,将为这些变量分配内存。
当推出这个代码块时,内存将被释放。
这样,在一个函数调用结束后,它的变量所占用的内存空间可以用来存储下一个被调用函数的变量。
到现在我们所使用的局部变量都属于自动类型。
{
int index ;
for ( index = 0 ; index < number ; index ++ )
{
puts ( " They don't make them the way they used to. / n " ) ;
return 0 ;
}
}
总论:c使用作用域、链接和存储时期来定义5中存储类。
自动,寄存器、具有代码块作用域的静态、具有外部链接的静态、具有内部链接的静态
1.自动变量
自动变量具有自动存储时期、代码块作用域和空链接。
默认情况下,在代码块或函数的头部定义的任意变量都属于自动存储类。
你可以显示的使用关键字auto来表明一个变量为自动变量,但我们一般不写。
{
auto int plox ;
}
int loop ( int n )
{
int m ; // m的作用域
scanf ( " %d " , & m ) ;
{
int i ; // m和i的作用域
for ( i = m ; i < n ; i ++ )
{
puts ( " i is local to a sub-block / n " ) ;
}
}
return m ; // m的作用域, i已经消失
}
如果在内层代码块定义了一个具有和外层代码块便来那个同一名字的变量,将会怎样?
(最好不要在你的代码中这样做)
这时内层代码块将使用在内层代码块中这个新定义的变量。
我们称之为内层定义覆盖(hide)了外部定义,只有当运行离开内层代码块是,外部变量才会重新恢复使用。
int main ( void )
{
int x = 30 ;
printf ( " x in outer block: %d / n " , x ) ;
{
int x = 77 ;
printf ( " x in inner block: %d / n " , x ) ;
}
}
注意:
c99中规定,语句若为循环或者if语句的一部分,即使没有使用{},也认为是一个代码块。
int main ( void )
{
int n = 10 ;
printf ( " Initially, n = %d / n " , n ) ;
for ( int n = 1 ; n < 3 ; n ++ )
printf ( " loop : n = %d / n " , n ) ;
printf ( " Initially, n = %d / n " , n ) ;
return 0 ;
}
注意:
除非您显式地初始化自动变量,否则它不会被自动初始化。
{
int repid ;
int tents = 5 ;
}
repid的初值则是先前占用分配给它的空间的任意值。不要指望这个值是0。
2.寄存器变量
通常,变量存储在计算机内存中。如果幸运,寄存器变量可以被存储在CPU的寄存器中,这样速度会更快。
在其它许多方面,它和自动变量是一样的。
通过使用存储类型说明符register可以声明寄存器变量。
{
register int quick ;
}
我们说如果幸运是因为声明一个寄存器类变量仅仅是一个请求,而不是一条直接命令。
因为CPU寄存器往往很少,编译器必须在可用寄存器的个数和可用高速内存的数量之间做权衡。
这种情况下,变量成为一个普通的自动变量。
但是,你不能对它使用地址运算符。
可以把一个形式参量请求为寄存器变量。
3.具有代码块作用域的静态变量
静态变量像是一个不可变的变量。
但实际上,静态指变量的的位置固定不动。
具有文件作用域的变量自动的具有静态存储时期。
也可以创建具有代码块作用域,兼具有静态存储的局部变量。
这些变量和自动变量具有相同的作用域,当包含这些变量的函数完成工作时,它们并不消失。
从一次函数调用到下一次调用,计算机都记录着他们的值。
我们可以使用存储类说明符static在代码块内声明这些变量。
void trystat ( void ) ;
int main ( void )
{
int count ;
for ( count = 1 ; count <= 3 ; count ++ )
{
printf ( " Here comes iteration %d: / n " , count ) ;
trystat () ;
}
return 0 ;
}
void trystat ( void )
{
int fade = 1 ;
static int stay = 1 ;
printf ( " fade = %d and stay = %d / n " , fade ++, stay ++ ) ;
}
注意:
对函数参数不能使用static。
4.具有外部链接的静态变量
具有外部链接的静态变量具有文件作用域、外部链接和静态存储时期。
这一类型的变量成为外部变量 external variable。
把变量的定义声明放在所有函数之外,即创建了一个外部变量。
为了使程序更加清晰,可以在使用外部变量的函数中通过使用extern关键字来再次声明它。
如果变量是在别的文件中定义的,使用extern来声明该变量就是必须的。
double Up [ 100 ] ; //外部声明的数组
extern char Coal ; //必须的声明,因为Coal在其它文件中定义
void next ( void ) ;
int main ( void )
{
extern int Errupt ; //可选的声明
extern double Up [] ; //可选的声明
}
void next ( void )
{
}
注意:
不同于自动变量,如果您不对外部变量进行初始化,他们将自动被赋初值0。
这一原则也适用于外部定义的数组。
5.具有内部静态链接的静态变量
通过使用存储类说明符(注意不是静态变量声明符)static在所有函数的外部进行定义,前面已经说过该变量为整个文件私有
,这里不再讨论。
static int stayhome = 1 ; //内部链接
一、类型限定词volatile
限定词volatile告诉编译器该变量除了可以被程序改变以外还可被其他代理改变。
它通常被用于硬件地址和与其它并行运行的程序共享的数据。
如:
volatile int * ploc ;
你可能会奇怪为什么ANSI觉得有必要把volatile作为一个关键字。
原因是它可以方便编译器优化。
例如
/* 一些不使用x的代码 */
val2 = x ;
一个聪明的编译器可能注意到你使用了两次x,而没有改变它的值。那么,它有可能把x临时存储在一个寄存器中。
接着,当val2需要x时,可以通过从寄存器而非初始的内存位置中读取该值以节省时间。
这个过程被称为缓存(caching)。
通常,缓存是一个好的优化方式,但是如果在两个语句间其他代理改变了x的话就不是这样了。
如果没有规定volatile关键字,那么编译器将无从得知这种改变是否可能发生。
在ANSI中,如果声明中没有volatile关键字,那么编译器就可以假定一个值在使用过程中没有被修改,它就可以试着优
化代码。
一个值可以同时是const 和 volatile。
例如,硬件时钟一般设定为不能由程序改变,这一点使它成为const;
但它被程序以外的代理改变,这使它成为volatile.
那么我们可以这么声明:
const volatile int * ploc ;
二、类型限定词restrict
关键字restrict用来消除数据间的相关性,编译器从而可以安排语句的并行执行。
它只可以用于指针,并表明指针是访问一个数据对象的唯一且初始的方式。
我们通过一个例子来看看:
int * par = ar ;
int * restrict restar = ( int * ) malloc ( 10 * sizeof ( int )) ;
注意,指针restar是访问由malloc ()分配的内存的唯一且初始的方式。
因此,它可以由关键字restrict限定。
而指针par既不是初始的,也不是访问数组ar中数据的唯一方式,因此不可以把它限定为restrict。
考虑下面的语句:
{
par [ n ] += 5 ;
restar [ n ] += 5 ;
ar [ n ] *= 2 ;
par [ n ] += 3 ;
restar [ n ] += 3 ;
}
知道了restar是放问它所指向数据块的唯一初始化方式,编译器就可以用具有同样效果的一条语句来代替包含restar的
两个语句。
然而,编译器将两个包含par的语句精简为一个语句将导致计算错误。
原因是ar[n] *= 2;这条语句在par[n] += 3之前已经改变了par指针所指向数据的值。
restrict的作用:帮助编译器确定使指针进行数值计算时,是否可以进行优化。
可以将关键字restrict作为指针型函数参量的限定词使用。
这意味着编译器可以假定在函数体内没有其它标识符修改指针指向的数据,因而可以试着优化代码,反之则不然。
关键字restrict有两个读者。
一个是编译器,它告诉编译器可以自由地去做一些有关优化的假定。
一个是用于,它告诉用户仅使用满足restrict要求的参数。