c prime plus 学习重点知识(存储类别与内存管理)

本文详细介绍了C语言中的存储类别,包括函数作用域、存储期的关键字(如auto、register、static、extern)以及内存管理的malloc和free。讲解了不同作用域变量的特性,如静态变量在函数调用间的保留,以及extern关键字的用途。同时讨论了volatile和restrict类型限定符在并发和优化中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数作用域

C变量有3种链接属性:外部链接、内部链接或无链接。具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味这些变量属于定义它们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中用,内部链接变量只能在一个翻译单元中使用。

正式用语与非正式用语
C标准用“内部链接的文件作用域”描述仅限于一个翻译单元(即一-个源代码文件和它所包含的头文件)的作用域,用“外部链接的文件作用域”描述可延伸至其他翻译单元的作用域。但是,对程序员而言这些术语太长了。一些程序员把“内部链接的文件作用城”简称为“文件作用域”,把“外部链接的文件作用域”简称为“全局作用域”或“程序作用域”。

如何知道文件作用域变量是内部链接还是外部链接?可以查看外部定义中是否使用了存储类别说明符static:
int giants = 5; //文件作用域,外部链接
static int dodgers = 3; //文件作用域,内部链接

存储期(面试常问)

有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。

线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_ Thread_ local 声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存:当退出这个块时,释放刚才为变量分的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于储存下一个被调用函数的变量。
块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前面加上关键字static:
static int a=0;
这里,变量a储存在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在函数块中。只有在执行该函数时,程序才能使用a访问它所指定的对象(但是,该函数可以给其他函数提供该存储区的地址以便间接访问该对象,例如通过指针形参或返回值)。

常见的重要的关键字

自动变量auto

属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。为了更清楚地表达你的意图(例如,为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别),可以显式使用关键字auto,如下所示:
int main (void)
auto int p1ox;
关键字auto是存储类别说明符(storage -class specifier)。 auto 关键字在C++中的用法完全不同,如.果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。

注:如果内层块中声明的变量与外层块中的变量同名会怎样?内层块会隐藏外层块的定义。但是离开内层.块后,外层块变量的作用域又回到了原来的作用域。下面演示了这一过程。
int x=30; //原始的x
printf(“x in outer block: %d at tp\n”, x,&x);
{
int x =77;
//新的x,隐藏了原始的x
printf(“x in inner block: %d at tp\n”, x,&x);
}

寄存器变量register

变量通常储存在计算机内存中。如果幸运的话,寄存器变量储存在CPU的寄存器中,或者概括地说,储存在最快的可用内存中。与普通变量相比,访问和处理这些变量的速度更快。由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址。绝大多数方面,寄存器变量和自动变量都一样。也就是说,它们都是块作用域、无链接和自动存储期。使用存储类别说明符register便可声明寄存器变量:
register int quick;
我们刚才说“如果幸运的话”,是因为声明变量为register类别与直接命令相比更像是一种请求。编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,所以可能不会如你所愿。在这种情况下,寄存器变量就变成普通的自动变量。即使是这样,仍然不能对该变量使用地址运算符。

静态变量static

这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。在块中(提供块作用域和无链接)以存储类别说明符static (提供静态存储期)声明这种变量。
void trystat (void)
{
int fade = 1;
static int stay = 1;
printf (“fade=%d and stay=%d\n”, fade++, stay++) ;
}
注:如果未显显式的初始化静态变量,他们会被初始化为0。

外部变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别有时称为外部存储类别(external storage class),属于该类别的变量称为外部变量( external variable)。把变量的定义性声明(defining declaration)放在在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中用关键字extern再次声明。如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变量。如下所示:
int Errupt; /外部定义的变量/
double Up[100] ; /外部定义的数组/
extern char Coal ;/*如果Coal被定义在另一个文件, */
/则必须这样声明/
注:如果不显式初始化,外部变量也会被初始化为0 。

extern关键字

考虑下面的例子:
int tern = 1; /* tern被定义*/
main(){
extern int tern; /* 使用在别处定义的tern */
}
这里,tern 被声明了两次。第1次声明为变量预留了存储空间,该声明构成了变量的定义。第2次声明只告诉编译器使用之前已创建的tern变量,所以这不是定义。第1次声明被称为定义式声明(defining
declaration),第2次声明被称为引用式声明(referencing declaration)。关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义。
假设这样写:
extern int tern;
int main (void)
{
编译器会假设tern实际的定义在该程序的别处,也许在别的文件中。该声明并不会引起分配存储空间。因此,不要用关键字extern创建外部定义,只用它来引用现有的外部定义。

注意
外部变量只能初始化一-次,且必须在定义该变量时进行。假设有下面的代码:
// file_ one.c
char permis=‘N’;
// file_ two.c
extern char permis = ‘Y’; /错误/
file_ two 中的声明是错误的,因为file_ one.c 中的定义式声明已经创建并初始化了permis。

内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。在所有丽数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有这种存储类别:
static int svil = 1; // 静态变量,内部链接
int main (void)
这种变量过去称为外部静态变量(external statie variable),但是这个术语有点自相矛盾(这些变量具有内部链接)。但是,没有合适的新简称,所以只能用内部链接的静态变量(statie variable with internal linkage)。普通的外部变量可用于同一程序中任意文件中的函数,但是内部链接的静态变量只能用于同一一个 文件中的函数。可以使用存储类别说明符extern, 在函数中重复声明任何具有文件作用域的变量。这样的声明并不会改变其链接属性。考虑下面的代码:
int traveler = 1;//外部链接
static int stayhome = 1;//内部链接
int main()
extern int traveler;
//使用定义在别处的traveler
extern int stayhome;
//使用定义在别处的stayhome

对于该程序所在的翻译单元,trveler 和stayhome都具有文件作用域,但是只有traveler可用于其他翻译单元(因为它具有外部链接)。这两个声明都使用了extern关键字,指明了main()中使用的这两个变量的定义都在别处,但是这并未改变stayhome的内部链接属性。

静态函数

static定义的函数同static定义的变量类似,只在本文件有效,在程序的其他文件是无法调用该文件的。
例如下面的代码:
double gamma (double) ;/该函数默认为外部函数/
static double beta (int,int) ;
extern double delta (double, int) ;
在同一个程序中,其他文件中的函数可以调用gamma ()和delta(),但是不能调用beta(),因为以static存储类别说明符创建的函数属于特定模块私有。这样做避免名称冲突的问题,由于beta()受限于它所在的文件,所以在其他文件中可以使用与之同名的函数。

内存管理

malloc和free

C。可以在程序运行时分配更多的内存。主要的工具是malloc()函数,该函数接受一个参数:所需的内存字节数。malloc ()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说,malloc ()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把.该地址赋给一个指针变量,并使用指针访问这块内存。因为char表示1字节,被定义为指向char的指针。
然而,从ANSIC标准开始,C使用一个新的类型:指向void的指针。该类型相当于一个“通用指针”。malloc() 函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。在ANSIC中,应该坚持使用强制类型转换,提高代码的可读性。然而,把指向void的指针赋给任意类型的指针完全不用考虑类型匹配的问题。
参考如下代码:
double * ptd;
ptd = (double *) malloc(30 * sizeof (double)) ;
以上代码为30个double类型的值请求内存空间,并设置ptd指向该位置。注意,指针ptd被声明为指向一个double类型,而不是指向内含30个double类型值的块。回忆一下, 数组名是该数组首元素的址。因此,如果让ptd指向这个块的首元素,便可像使用数组名-样 使用它。

通常,malloc() 要与free()配套使用。free ()函数的参数是之前malloc ()返回的地址,该函数释放之前malloc()分配的内存。因此,动态分配内存的存储期从调用malloc()分配内存到调用free ()释放内存为止。

volatile 类型限定符

volatile限定符告知计算机,代理( 而不是变量所在的程序)可以改变该变量的值。通常,它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。例如,一个地址上可能储存着当前的时钟时间,无论程序做什么,地址上的值都随时间的变化而改变。或者一个地址用于接受另-台计算机传入的信息。

restrict 类型限定符

restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。 要弄明白为什么这样做有用,先看几个例子。考虑下面的代码:
int ar[10];
int *restrict restar = (int *) malloc(10 *sizeof (int) ) :
int *par=ar;
这里,指针restar是访问由malloc ()所分配内存的唯一且初始的方式。因此,可以用restrict关键字限定它。而指针par既不是访问ar数组中数据的初始方式,也不是唯一方式。 所以不用把它设置
为restrict。

内存分配与释放时间(面试常问)

静态存储类别所用的内存数量在编译时确定,只要程序还在运行,就可访问储存在该部分的数据。该类别的变量在程序开始执行时被创建,在程序结束时被销毁。
然而,自动存储类别的变量在程序进入变量定义所在块时存在,在程序离开块时消失。因此,随着程序调用函数和函数结束,自动变量所用的内存数量也相应地增加和减少。这部分的内存通常作为栈来处理,这意味着新创建的变量按顺序加入内存,然后以相反的顺序销毁。
动态分配的内存在调用malloc()或相关函数时存在,在调用free ()后释放。这部分的内存由程序员管理,而不是一套规则。所以内存块可以在一个函数中创建,在另一个函数中销毁。正是因为这样,这部分的内存用于动态内存分配会支离破碎。也就是说,未使用的内存块分散在已使用的内存块之间。另外,使用动态内存通常比使用栈内存慢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值