存储类、链接和内存管理

变量:

1、存储时期:变量在内存中保留的时间

2、变量的作用域和链接:一起表明程序的哪些部分可以通过变量名来使用该变量

不同的存储类型提供了变量的作用域、链接以及存储时期的不同组合。

多个不同的源文件共享的变量某个特定文件中的所有函数都可以使用的变量只有在某个特定函数中才可以使用的变量只有某个函数的一小部分内可以使用的变量

作用域:代码块作用域、函数原型作用域或者文件作用域

文件作用域(file scope):一个在所有函数之外定义的变量。具有文件作用域的变量从它定义处到包含该定义的文件结尾处都是可见的。

#include <stdio.h>
int uints=0;
void critic(void);
int main(void)
{
    ...
}
void critic(void)
{
    ...
}
变量uints具有文件作用域,在main()和critic()中都可以使用它。因为它们可以在不止一个函数中使用,文件作用域变量也被称为 全局变量(global variable)

链接

外部链接(external linkage)内部链接(internal linkage)空链接(no linkage)
具有代码块作用域或者函数原型作用域的变量有空链接,意味着它们是由其定义所在的代码块或函数原型所私有的。
具有文件作用域的变量可能有内部或者外部链接。一个具有外部链接的变量可以在一个多文件程序的任何地方使用。一个具有内部链接的变量可以在一个文件的任何地方使用。
通过观察在外部定义中是否使用了存储类说明符static可以知道一个文件作用域具有内部链接还是外部链接。
int giants=5;           //文件作用域,外部链接
static int dodgers=3;   //文件作用域,内部链接
int main(void)
{
    ...
}
和该文件属于同一程序的其他文件可以使用变量giants。变量dodgers是该文件私有的,但是可以被该文件中任一函数使用。

存储时期

静态存储时期(static storage duration)自动存储时期(automatic storage duration)
具有静态存储时期的变量在程序执行期间一直存在。具有文件作用域的变量具有静态存储时期。所有文件作用域变量,无论它具有内部链接还是具有外部链接,都具有静态存储时期。
具有自动存储时期的变量在程序进入定义这些变量的代码块时,将为这些变量分配内存;当退出这个代码块时,分配的内存将被释放。具有代码块作用域的变量一般情况下具有自动存储时期。
作用域链接存储时期
代码块作用域空链接自动存储时期
函数原型作用域空链接自动存储时期
文件作用域内部链接/外部链接静态存储时期

C使用作用域、链接和存储时期来定义5中类型:自动、寄存器、具有代码块作用域的静态、具有外部链接的静态,以及具有内部链接的静态。
5中存储类
存储类时期作用域链接声明方式
自动自动代码块代码块内
寄存器自动代码块代码块内,使用关键字register
具有外部链接的静态静态文件外部所有函数之外
具有内部链接的静态静态文件内部所有函数之外,使用关键字static
空链接的静态静态代码块代码块内,使用关键字static

1、自动变量

在内层代码块定义的名字是内层代码块所使用的变量。我们称之为内层定义覆盖(hide)了外部定义,但当运行离开内层代码块时,外部变量重新恢复作用。
在程序执行代码块内语句时,代码块作用域的变量覆盖了具有文件作用域的同名变量。

2、寄存器变量

通常,变量存储在计算机内存中。寄存器变量可以被存储在CPU寄存器中,或更一般地,存储在速度最快的可用内存中,从而可以比普通变量更快地被访问和操作。
因为寄存器变量多是存放在一个寄存器而非内存中,所以无法获得寄存器变量的地址。

3、具有代码块作用域的静态变量/内部静态存储类

具有文件作用域的变量自动(也是必须的)具有静态存储时期。也可以创建具有代码块作用域,兼具静态存储的局部变量。这些变量和自动变量具有相同的作用域,但当包含这些变量的函数完成工作时,它们并不消失。也就是说,这些变量具有代码块作用域、空链接,却有静态存储时期。从一次函数调用到下一次调用,计算机都记录它们的值。这样的变量通过使用存储类说明符static(这提供了静态存储时期)在代码块内声明(这提供了代码块作用域和空链接)创建。

4、具有外部链接的静态变量

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

1、外部变量初始化

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

2、定义和声明

int tern=1;              //定义tern
int main(void)
{
    extern int tern;    //使用在其他地方定义的tern变量
    ......
}
tern声明了两次。
第一次声明为变量留出了存储空间。它构成了变量的定义。
第二次声明只是告诉编译器要使用先前定义的变量tern,因此不是一个定义。
第一次声明称为 定义声明(defining declaration),第二次声明称为 引用声明(referencing declaration)。关键字extern表明该声明不是一个定义,因为它指示编译器参考其他地方。
extern int tern;
int main(void)
{
    ......
}
编译器假定tern的真正定义是在程序中其他某个地方,也许是在另一个文件中。这样的声明不会引起空间分配。因此, 不要用关键字extern来进行外部定义;只用它来引用一个已经存在的外部定义。
一个外部变量只可进行一次初始化,而且一定是在变量被定义时进行。下面的语句是错误的:
extern char permis='Y';     //错误
因为关键字extern的存在标志着这是一个引用声明,而非定义声明。

5、具有内部链接的静态变量

这类存储类的变量具有静态存储时期、文件作用域以及内部链接。通过使用存储类说明符static在所有函数外部进行定义(正如定义外部变量那样)来创建一个这样的变量。
可以在函数中使用存储类说明符extern来再次声明任何具有文件作用域的变量。这样的声明并不改变链接。
int traveler=1;         //外部链接
static int stayhome=1;  //内部链接
int main(void)
{
    extern int traveler;    //使用全局变量traveler
    extern int stayhome;    //使用全局变量stayhome
    ......
}
对这个文件来说traveler和stayhome都是全局的,但只有traveler可以被其他文件中的代码使用。使用extern的两个声明表明main()在使用两个全局变量,但stayhome仍具有内部链接。

二、存储类说明符

C语言有5个作为存储类说明符的关键字,它们是auto、register、static、extern以及typedef。关键字typedef与内存存储无关,由于语法原因被归入此类。

三、存储类和函数

函数也有存储类。函数可以是外部的(默认情况下)或者静态的。外部函数可被其他文件中的函数调用,而静态函数只可以在定义它的文件中使用。
double gamma();         //默认为外部的
static double beta();
extern double delta();
函数gamma()和delta()可被程序的其他文件中的函数使用,而beta()则不可以。因为beta()被限定在一个文件中,故 可在其他文件中使用具有相同名称的不同的函数。使用static存储类的原因之一就是创建为一个特定模块所私有的函数,从而避免可能的名字冲突。
通常使用关键字extern来声明在其他文件中定义的函数。这一习惯做法 主要是为了使程序更清晰,因为除非函数声明使用了关键字static,否则认为它是extern的。

四、分配内存:malloc()和free()

这5中存储类有一个共同之处:在决定了使用哪一存储类之后,就自动决定了作用域和存储时期。您的选择服从预先制定的内存管理规则。另外,还可以使用库函数来分配和管理内存。
C可以在程序运行时分配更多的内存,主要工具是函数malloc(),它接受一个参数:所需内存字节数。然后malloc()找到可用内存中一个大小合适的块。内存是匿名的:也就是说,malloc()分配了内存,但没有为它指定名字。然而,它却可以返回那块内存第一个字节的地址。因此, 您可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。
例如,我们使用malloc()来创建一个数组。可以在程序运行时使用malloc()请求一个存储块,另外还需要一个指针来存放该块在内存中的位置。
double *ptd;
ptd=(double *)malloc(30*sizeof(double));
注意:ptd是作为指向一个double类型值得指针声明的,而不是指向30个double类型值的数据块的指针。



创建一个动态数组(dynamic array),即一个在程序运行时才分配内存并可在程序运行时选择大小的数组。例如,假定n是一个整数变量。在C99之前,不能这样做:
double item[n];         //如果n是一个变量,C99之前不允许这样做
然而,即使在C99之前的编译器中,也可以这样做:
ptd=(double *)malloc(n*sizeof(double));

一般地,对应每个malloc()调用,应该调用一次free()。函数free()的参数是先前malloc()返回的地址,它释放先前分配的内存。这样,所分配内存的持续时间从调用malloc()分配内存开始,到调用free()释放内存以供再使用为止。不能使用free()来释放通过其他方式(例如声明一个数组)分配的内存。
通过malloc()函数,程序可以在运行时决定需要多大的数组并创建它。
ptd=(double *)malloc(max*sizeof(double))
上边行分配对于存放所请求数目的项来说足够大的内存,并将该内存块的地址赋给指针ptd。


内存分配还可以使用calloc()。典型的应用如下:
</pre></div><pre name="code" class="objc">long *newmem;
newmem=(long*)calloc(100,sizeof(long));
这个函数接受两个参数,都应是无符号的整数(在ANSI中时size_t类型)。第一个参数是所需内存单元的数量,第二个参数是每个单元以字节计的大小。
函数free()也可以用来释放由calloc()分配的内存。

动态内存分配与变长数组

int vlamal()
{
    int n;
    int *pi;
    pi=(int *)malloc(n*sizeof(n));
    int ar[n];
    pi[2]=ar[2]=-5;
    ......
}
一个区别在于变长数组(Variable-Length Array,VLA)是自动存储的。自动存储的结果之一就是VLA所用的内存空间在运行完定义部分之后会自动释放。在本例中,就是函数vlamal()终止的时候。因此不必使用free()。另一方面,使用由malloc()创建的数组不必局限在一个函数中。例如,函数可以创建一个数组并返回指针,供调用该函数的函数访问。接着,后者可以在它结束时调用free()。free()可以使用不同于malloc()指针的指针变量;必须一致的是指针中存储的地址。

程序将可用的内存分成了三个独立的部分:一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的;一个是自动变量的;另一个是动态分配的内存的。
动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。由程序员而不是一系列固定的规则控制内存持续时间,因此内存块可在一个函数中创建,而在另一个函数中释放。由于这点,动态内存分配所用的内存部分可能变为碎片状,也就是说,在活动的内存块之间散布着未使用的字节片。不管怎样,使用动态内存往往导致进程比使用堆栈内存慢。

ANSI C的类型限定词

一个变量是以它的类型和存储类表征的。C90增加了两个属性:不变性(const)和易变性(volatile)。C99标准添加了第三个限定词(restrict),用以方便编译器优化。

类型限定词const

如果变量声明中带有关键字const,则不能通过赋值、增量或减量运算来修改该变量的值。
例如,可以用关键字const创建一组程序不可以改变的数据。
const int days[12]={31,28,31,30,31,30,31,31,30,31,30,31}

在指针和参量声明中使用const

让指针本身称为const让指针指向的值成为const区分开来。
下面的声明表明pf指向的值必须是不变的,但pf本身的值可以改变。例如,它可以指向另一个const值。
const float * pf;        //pf指向一个常量浮点数值
相反,下面的声明表明指针pt本身的值是不可以改变的,它必须总是指向同一个地址,但所指向的值可以改变。
float * const pt;        //pt是一个常量指针
最后,下面的声明表明ptr必须总是指向同一个位置,并且它所指位置存储的值也不能改变。
const float * const ptr;

另外,float const *pfc;等同于const float *pfc;把const放在类型名的后边和*的前边,意味着指针不能够用来改变它所指向的值。 总而言之,一个位于*左边任意位置的const使得数据成为常量,而一个位于*右边的const使得指针自身成为常量。
const关键字的一个常见用法是声明作为函数形式参量的指针。

对全局数据使用const

使用全局变量被认为是一个冒险的方法,因为它暴露了数据,使程序的任何部分都可以错误地修改数据。如果数据是const的,这种危险就不存在了,因此对全局数据使用const限定词是很合理的。

类型限定词volatile

限定词volatile告诉编译器该变量除了可被程序改变以外还可被其他代理改变。典型地,它被用于硬件地址和其他并行运行的程序共享的数据。例如,一个地址中可能保持着当前的时钟时间。不管程序做些什么,该地址的值都会随着时间而改变。
一个值可以同时是const和volatile。例如,硬件时钟一般设定为不能由程序改变,这一点使它成为const;但它可以被程序以外的代理改变,这使它成为volatile。只是在声明中同时使用这两个限定词,如下所示;顺序并不重要。
volatile const int loc;
const volatile int * ploc;

类型限定词restrict

关键字restrict通过允许编译器优化某几种代码增强了计算支持。它只可用于指针,并标明指针是访问一个数据对象的唯一且初始的方式。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值