阅读 ‧ 电子书库 对C99的支持
有些编译器可能不支持这些新的C99作用域规则。其他的编译器可能提供一个激活这些规则的选项。例如,在编写本书的时候,gcc默认地支持很多C99特性,但是需要使用-std=c99选项来激活这些规则:
阅读 ‧ 电子书库

在第一个for循环的控制部分中声明的n到该循环末尾一直起作用,覆盖了初始的n。但在运行完该循环后,初始的n恢复作用。

在第二个for循环中,n声明为一个循环索引,覆盖了初始的n。接着,在循环体内声明的n覆盖了循环索引n。当程序执行完循环体后,在循环体内声明的n消失,循环判断使用索引n。整个循环终止时,初始的n又恢复作用。

二、自动变量的初始化

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

除非您显式地初始化自动变量,否则它不会被自动初始化。考虑下列声明:

阅读 ‧ 电子书库

变量tents初始化为5,而变量repid的初值则是先前占用分配给它的空间的任意值。不要指望这个值是0。倘若一个非常量表达式中所用到的变量先前都定义过的话,可将自动变量初始化为该表达式:

阅读 ‧ 电子书库

12.1.5 寄存器变量

通常,变量存储在计算机内存中。如果幸运,寄存器变量可以被存储在CPU寄存器中,或更一般地,存储在速度最快的可用内存中,从而可以比普通变量更快地被访问和操作。因为寄存器变量多是存放在一个寄存器而非内存中,所以无法获得寄存器变量的地址。但在其他的许多方面,寄存器变量与自动变量是一样的。也就是说,它们都有代码块作用域、空链接以及自动存储时期。通过使用存储类说明符register可以声明寄存器变量:

阅读 ‧ 电子书库

我们说“如果幸运”是因为声明一个寄存器类变量仅是一个请求,而非一条直接的命令。编译器必须在您的请求与可用寄存器的个数或可用高速内存的数量之间做权衡,所以您可能达成不了自己的愿望。这种情况下,变量成为一个普通的自动变量;然而,您依然不能对它使用地址运算符。

可以把一个形式参量请求为寄存器变量。只需在函数头部使用register关键字:

阅读 ‧ 电子书库

可以使用register声明的类型是有限的。例如,处理器可能没有足够大的寄存器来容纳double类型。

12.1.6 具有代码块作用域的静态变量

静态变量(static variable)这一名称听起来很矛盾,像是一个不可变的变量。实际上,“静态”是指变量的位置固定不动。具有文件作用域的变量自动(也是必须的)具有静态存储时期。也可以创建具有代码块作用域,兼具静态存储的局部变量。这些变量和自动变量具有相同的作用域,但当包含这些变量的函数完成工作时,它们并不消失。也就是说,这些变量具有代码块作用域、空链接,却有静态存储时期。从一次函数调用到下一次调用,计算机都记录着它们的值。这样的变量通过使用存储类说明符static(这提供了静态存储时期)在代码块内声明(这提供了代码块作用域和空链接)创建。程序清单12.3中的例子说明了这一技术。

程序清单12.3 loc_stat.c程序

阅读 ‧ 电子书库

注意,trystat()在打印出每个变量的值后递增变量。运行程序将返回下列结果:

阅读 ‧ 电子书库

静态变量stay记得它的值曾被加1,而变量fade每次都重新开始。这表明了初始化的不同:在每次调用trystat()时fade都被初始化,而stay只在编译trystat()时被初始化一次。如果不显式地对静态变量进行初始化,它们将被初始化为0。

下面两个声明看起来很相似:

阅读 ‧ 电子书库

然而,第一个语句确实是函数trystat()的一部分,每次调用该函数时都会执行它。它是个运行时的动作。而第二个语句实际上并不是函数trystat()的一部分。如果用调试程序逐步运行该程序,您会发现程序看起来跳过了那一步。那是因为静态变量和外部变量在程序调入内存时已经就位了。把这个语句放在trystat()函数中是为了告诉编译器只有函数trystat()可以看到该变量。它不是在运行时执行的语句。

对函数参量不能使用static:

阅读 ‧ 电子书库

阅读一些老的C文献时,您会发现该存储类被归为内部静态存储类。然而,这里的内部一词被用来表明在函数内部,而不是内部链接。

12.1.7 具有外部链接的静态变量

具有外部链接的静态变量具有文件作用域、外部链接和静态存储时期。这一类型有时被称为外部存储类(external storage class),这一类型的变量被称为外部变量(external variable)。把变量的定义声明放在所有函数之外,即创建了一个外部变量。为了使程序更加清晰,可以在使用外部变量的函数中通过使用extern关键字来再次声明它。如果变量是在别的文件中定义的,使用extern来声明该变量就是必须的。应该像这样声明:

阅读 ‧ 电子书库

Errupt的两次声明是个链接的例子,因为它们都指向同一变量。外部变量具有外部链接,稍后我们将再提到这一点。

请注意不必在double Up的可选声明中指明数组大小。第一次声明已提供了这一信息。因为外部变量具有文件作用域,它们从被声明处到文件结尾都是可见的,所以main()中的一组extern声明完全可以省略掉。而它们出现在那里,作用只不过是表明main()函数使用这些变量。

如果函数中的声明漏掉了extern,就会建立一个独立的自动变量。也就是说,如果在main()中用:

阅读 ‧ 电子书库

替换:

阅读 ‧ 电子书库

将使编译器创建一个名为Errupt的自动变量。它将是一个独立的局部变量,而不同于初始的Errupt。在程序执行main()时该局部变量会起作用;但在像next()这种同一文件内的其他函数中,外部的Errupt将起作用。简言之,在程序执行代码块内语句时,代码块作用域的变量覆盖了具有文件作用域的同名变量。

外部变量具有静态存储时期。因此,数组Up一直存在并保持其值,不管程序是否在执行main()、next()还是其他函数。

下列3个例子展示了外部变量和自动变量的4种可能组合。例1中有一个外部变量:Hocus。它对main()和magic()都是可见的。

阅读 ‧ 电子书库

例2中有一个外部变量Hocus,对两个函数都是可见的。这次,magic()通过默认方式获知外部变量。

阅读 ‧ 电子书库

在例3中,创建了4个独立的变量。main()中的Hocus默认为自动变量,而且是main()的局部变量。magic()中的Hocus被显式地声明为自动变量,只对magic()可见。外部变量Hocus对main()或magic()不可见,但对文件中其他不单独拥有局部Hocus的函数都可见。最后,Pocus是一个外部变量,对magic()可见而对main()不可见,因为Pocus的声明在main()之后。

阅读 ‧ 电子书库

这些例子说明了外部变量的作用域:从声明的位置开始到文件结尾为止。它们也说明了外部变量的生存期。外部变量Hocus和Pocus存在的时间与程序运行时间一样,并且它们不局限于任一函数,在一个特定函数返回时并不消失。

一、外部变量初始化

和自动变量一样,外部变量可以被显式地初始化。不同于自动变量的是,如果您不对外部变量进行初始化,它们将自动被赋初值0。这一原则也适用于外部定义的数组元素。不同于自动变量,只可以用常量表达式来初始化文件作用域变量:

阅读 ‧ 电子书库

(只要类型不是一个变长数组,sizeof表达式就被认为是常量表达式。)

二、外部变量的使用

我们来看一个包含有外部变量的简单例子。特别地,假设需要两个分别叫作main()和critic()的函数来访问变量units。可以如程序清单12.4所示,在这两个函数之外的开始处声明变量units。

程序清单12.4 global.c程序

阅读 ‧ 电子书库

下面是一个输出示例:

阅读 ‧ 电子书库

注意函数critic()是怎样读取units的第二个值的;当main()结束while循环时,也知道了新值。因此,两个函数main()和critic()都用标识符units来访问同一个变量。在C的术语中,称units具有文件作用域、外部链接以及静态存储时期。

通过在所有函数定义的外面(外部)定义变量units,它成为一个外部变量。要使units对文件中随后的全部函数都可用,只需像前面这样做即可。

来看一些细节。首先,units声明所在的位置使得它对后面的函数可用,而不需采取任何其他操作。这样,critics()就可以使用变量units。

与之类似,也不需要做任何事来允许main()访问units。然而,main()中确实有如下的声明:

阅读 ‧ 电子书库

在这个例子中,声明主要是使程序的可读性更好。存储类说明符extern告诉编译器在该函数中用到的units都是指同一个在函数外部(甚至在文件之外)定义的变量。再次,main()和critic()都使用了外部定义的units。

三、外部名字

C99标准要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符。这修订了以前的要求:识别局部标识符的前31个字符和外部标识符的前6个字符。因为C99标准相对新一些,很可能您还是依照旧规则工作。对外部变量名字规定比对局部变量名字规定更严格,是因为外部名字需要遵守局部环境的规则,而该规则可能是有更多限制的。

四、定义和声明

我们来更为仔细地看一下变量定义与变量声明的区别。考虑下面的例子:

阅读 ‧ 电子书库

这里,tern声明了两次。第一次声明为变量留出了存储空间。它构成了变量的定义。第二次声明只是告诉编译器要使用先前定义的变量tern,因此不是一个定义。第一次声明称为定义声明(defining declaration),第二次声明称为引用声明(referencing declaration)。关键字extern表明该声明不是一个定义,因为它指示编译器参考其他地方。

如果这样做:

阅读 ‧ 电子书库

那么编译器假定tern的真正定义是在程序中其他某个地方,也许是在另一个文件中。这样的声明不会引起空间分配。因此,不要用关键字extern来进行外部定义;只用它来引用一个已经存在的外部定义。

一个外部变量只可进行一次初始化,而且一定是在变量被定义时进行。下面的语句是错的:

阅读 ‧ 电子书库

因为关键字extern的存在标志着这是一个引用声明,而非定义声明。

12.1.8 具有内部链接的静态变量

这种存储类的变量具有静态存储时期、文件作用域以及内部链接。通过使用存储类说明符static在所有函数外部进行定义(正如定义外部变量那样)来创建一个这样的变量:

阅读 ‧ 电子书库

以前称这类变量为外部静态(external static)变量,但因为它们具有内部链接,因此有点让人困惑。很不幸,没有新的简称来代替外部静态一词,只能使用“具有内部链接的静态变量”(static variable with internal linkage)。普通的外部变量可以被程序的任一文件中所包含的函数使用,而具有内部链接的静态变量只可以被与它在同一个文件中的函数使用。可以在函数中使用存储类说明符extern来再次声明任何具有文件作用域的变量。这样的声明并不改变链接。考虑如下代码:

阅读 ‧ 电子书库

阅读 ‧ 电子书库

对这个文件来说traveler和stayhome都是全局的,但只有traveler可以被其他文件中的代码使用。使用extern的两个声明表明main()在使用两个全局变量,但stayhome仍具有内部链接。

12.1.9 多文件

只有在使用一个由多文件构成的程序时,内部链接和外部链接的区别才显得重要,因此我们简要地谈一下这个问题。

复杂的C程序往往使用多个独立的代码文件。有些时候,这些文件可能需要共享一个外部变量。ANSI C通过在一个文件中定义变量,在其他文件中引用声明这个变量来实现共享。也就是说,除了一个声明(定义声明)外,其他所有声明都必须使用关键字extern,并且只有在定义声明中才可以对该变量进行初始化。

注意:除非在第二个文件中也声明了该变量(通过使用extern),否则在一个文件中定义的外部变量不可以用于第二个文件。一个外部变量声明本身只是使一个变量可能对其他文件可用。

然而历史上,许多编译器对这一问题遵循了不同的规则。例如在许多UNIX系统中,如果包含初始化的外部变量声明不超过一个的话,允许在多个文件中来声明该变量而不使用extern关键字。如果有一个包含初始化的声明,该声明就被当作变量的定义。