C Primer Plus(6) 中文版 第12章 存储类别、链接和内存管理 12.1 存储类别

C Primer Plus(6) 中文版 第12章 存储类别、链接和内存管理 12.1 存储类别
摘要由CSDN通过智能技术生成

程序员通过C的内存管理系统指定变量的作用域和生命期,实现对程序的控制。合理使用内存存储类型是设计程序的一个要点。
12.1 存储类别
C提供了多种不同的模型或存储类别(storage class)在内存中存储数据。
本书目前所有编程示例中使用的数据都存储在内存中。从硬件方面来看,被存储的每个值都占用一定的物理内存,C语言把这样的一块内存称为对象(object)。对象可以存储一个或多个值。一对象可能并未存储实际的值,但是它在存储适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行的操作,C不是面向对象编程语言)。
从软件方面来看,程序需要一种方法访问对象。这可以通过声明变量来完成:
int entity = 3;
该声明创建了一个名为entity的标识符(identifier)。标识符是一个名称,在这种情况下,标识符可以用来指定(designate)特定对象的内容。标识符遵循变量的命名规则。在该例中,标识符entity即是软件(即C程序)指定硬件内存中的对象的方式。该声明还提供了存储在对象中的值。
变量名不是指定对象的唯一途径。考虑下面的声明:
int *pt = &entity;
int ranks[10];
第1行声明中,pt是一个标识符,它指定了一个存储地址的对象。但是,表达式*pt不是标识符,因为它不是一个名称。然而,它确实指定了一个对象,在这种情况下,它与entity指定的对象相同。一般而言,那些指定对象的表达式被称为左值。所以,entity既是标识符也是左值;*pt既是表达式也是左值。顺带一提,ranks的声明创建了一个可容纳10个int类型元素的对象,该数组的每个元素也是一个对象。
如果可以使用左值改变对象中的值,该左值就是一个可修改的左值(midifiable lvalue)。考虑下面的声明:
const char *pc = "Behold a string literal!";
程序根据该声明把相应的字符串常量存储在内存中,内含这些字符值的字符串字面量就是一个对象。由于字符串字面量中的每个字符都能被单独访问,所以每个字符也是一个对象。该声明还创建了一个标识符为pc的对象,存储着字符串的地址。由于可以设置pc重新指向其他字符串,所以标识符pc是一个可修改的左值。const只能保证被pc指向的字符串内容不被修改,但是无法保证pc不指向别的字符串。由于*pc指定了存储'B'字符的数据对象,所以*pc是一个左值,但不是一个可修改的左值。与此类似,因为字符串字面量本身指定了存储字符串的对象,所以它也是一个左值,但不是可修改的左值。
可以用存储期(storage duration)描述对象,所以存储期是指对象在内存中保留了多长时间。标识符用于访问对象,可以用作用域(scope)和链接(linkage)描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它。不同的存储类型具有不同的存储器、作用域和链接。
标识符可以在源代码的多文件中共享、可用于特定文件的任意函数中、可仅限于特定函数中使用,甚至只在函数中的某部分使用。对象可存在于程序的执行期,也可以仅存于它所在函数的执行期。对于并发编程,对象可以在特定线程的执行期存在。可以通过函数调用的方式显式分配和释放内存。 
12.1.1 作用域
作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
块是用一对花括号括起来的代码区域。定义在块中的变量具有块作用域(block scope),块作用域变量的可见范围是从定义处到包含该定义的块的末尾。另外,虽然函数的形式参数生命在函数的左花括号之前,但是它们也具有块作用域,属于函数体这个块。例如,下面代码中的变量
cleo和patrick都具有块作用域:
double block( double cleo ){
    double patrick = 0.0;
    ...
    return patrick;

声明在内层块中的变量,其作用域仅局限于该声明所在的块:
double block( double cleo ){
    double patrick = 0.0;
    int i;
    for( i = 0; i < 10; i++ ){
        double q = cleo * i; //q的作用域开始
        ...
        patrick *= q; 
    }                     //q的作用域结束 
    ...
    return patrick;

以前,具有块作用域的变量都必须声明在块的开头。C99标准放宽了这一限制,允许在块中的任意位置声明变量。因此,对于for的循环头,现在可以这样写:
for( int i = 0; i < 10; i++ ){
    printf( "A C99 feature: i = %d", i );

为适应这个新特性,C99把块的概念扩展到包括for循环、while循环、do while循环和if语句所控制的代码,即使这些代码没有用花括号括起来,也算是块的一部分。所以,上面for循环中的变量i被视为for循环块的一部分,它的作用域仅限于for循环。一旦程序离开for循环,不能再访问i。
函数作用域(function scope)仅用于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。如果在两个块中使用相同的标签会很混乱,标签的函数作用域防止了这样的事情发生。
函数原型作用域(function prototype scope)用于函数原型中的形参名(变量名),如下所示:
int mighty( int mouse, double large );
函数原型作用域的范围是从形参定义到原型声明结束。这意味着,编译器处理函数原型中的形参时只关心它的类型,而形参名(如果有的话)通常无关紧要。而且,即使有形参名,也不必与函数定义中的形参名相匹配。只有在变长数组中,形参名才有用:
void use_a_VLA( int n, int m, int ar[n][m] );
方括号必须使用在函数原型中已声明的名称。
变量的定义在函数的外面,具有文件作用域(file scope)。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见。例如:
#include <stdio.h>
int units = 0; /*该变量具有文件作用域*/
void critic( void );
int main( void ) {
    ...

void critic( void ){
    ...
}
这里,units具有文件作用域,main()和critic()函数都可以使用它(更准确地说,units具有外部链接文件作用域)。由于这样的变量可用于多个函数,所以文件作用域变量也称为全局变量(global variable)。
注意 翻译单元和文件
你认为的多个文件在编译器可能以一个文件出现。例如,通常在源代码(.c扩展名)中包含一个或多个头文件(.h扩展名)。头文件会依次包含其他头文件,所以会包含多个单独的物理文件。但是,C预处理器实际上是用包含的头文件内容替换#include指令。所以,编译器把源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元(translation unit)。描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元。如果程序由多个源代码文件组成,那么该程序也将由多个翻译单元组成,每个翻译单元均对应一个源代码文件和它所包含的文件。
12.1.2 链接
C变量有3种链接属性:外部链接、内部链接或无链接。具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值