第十一章 存储类、链接和内存管理

11.1存储类

  C为变量提供了5种存储类型,或称存储类。自动、寄存器、静态和空链接、静态和外部链接以及静态和内部链接。

  存储时期就是变量在内存中保留的时间,变量的作用域和链接一起表明程序的哪些部分可以通过变量名来使用该变量。不同的存储类提供了变量的作用域、链接以及存储时期的不同组合。

●自动­­­­­­——在一个代码块内(或在一个函数头部作为参量)声明的变量,无论有没有存储类修饰符auto,都属于自动存储类。该类具有自动存储时期、代码块作用域和空链接。如未经初始化,它的值是不定的。

●寄存器——在一个代码块内(或在一个函数头部作为参量)使用存储类修饰符register声明的变量属于寄存器存储类。该类具有自动存储时期、代码块作用域和空链接,并且无法获得其地址。

●静态、空链接——在一个代码块内使用存储类修饰符static声明的变量属于静态空链接存储类。该类具有静态存储时期、代码块作用域和空链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。

●静态、外部链接——在所有函数外部定义、使用存储类修饰符static的变量属于静态、外部链接存储类。该类具有静态存储时期、文件作用域和外部链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。

●静态、内部链接——在所有函数外部定义、使用存储类修饰符static的变量属于静态、内部链接存储类。该类具有静态存储时期、文件作用域和内部链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。

11.1.1作用域

  一个C变量的作用域可以是任何代码块作用域、函数原型作用域,或者文件作用域。

  传统上,具有代码块作用域的变量都必须在代码块的开始处进行声明。C99放宽了这一规则,允许在一个代码块中任何位置声明变量。一个新的可能是变量声明可以出现在for循环的控制部分。

  作为这一新功能的一部分,C99把代码块的概念扩大到包括由for循环、while循环、do while循环或者if语句所控制的代码——即使这些代码没有用花括号括起来。因此在前述for循环中,变量i被认为是for循环代码块的一部分。这样它的作用域就限于这个for循环,程序的执行离开for循环后就不能再看到变量i了。  

  int mighty(int mouse, double large);

  函数原型作用域从变量定义处一直到原型声明的末尾。这意味着编译器在处理一个函数原型的参数时,它所关心的只是该参数的类型;您使用什么名字(如果使用了的话)通常是无关紧要的,不需要使它们和在函数定义中使用的变量名保持一致。

  一个函数之外定义的变量具有文件作用域。具有文件作用域的变量从它定义处到包含该定义的文件结尾处都是可见的。文件作用域变量也被称为全局变量。

11.1.2链接

  一个C变量具有下列链接之一:外部链接,内部链接,或空链接。具有代码块作用域或者函数原型作用域的变量有空链接,意味着它们是由其定义所在的代码块或函数原型所私有的。具有文件作用域的变量可能有内部链接或者外部链接。一个具有外部链接的变量可以在一个多文件程序的任何地方使用。一个具有内部链接的变量可以在一个文件的任何地方使用。

  那么怎样知道一个文件作用域变量是内部链接还是外部链接呢?可以看在外部定义中是否使用了存储类说明符static。

11.1.3存储时期

  一个C变量有以下两种存储时期之一:静态存储时期和自动变量存储时期。如果一个变量具有静态存储时期,它在程序执行期间将一直存在。具有文件作用域的变量具有静态存储时期。注意对于具有文件作用域的变量,关键词static表明链接类型,并非存储时期。一个使用static声明了的文件作用域变量具有内部链接,而所有的文件作用域变量,无论它具有内部链接还是具有外部链接,都具有静态存储时期。

  具有代码块作用域的变量一般情况具有自动存储时期。在程序进入定义这些变量的代码块时,将为这些变量分配内存;当退出这个代码块时,分配的内存将被释放。该思想把自动变量的内存视为一个可以重复使用的工作区或者暂存内存。

存储类 时期 作用域 链接 声明方式
自动 自动 代码块 代码块内
寄存器 自动 代码块 代码块内,使用关键字register
具有外部链接的静态 静态 文件 外部 所有函数之外
具有内部链接的静态 静态 文件 内部 所有函数之外,使用关键字static
空链接的静态 静态 代码块 代码块内 ,使用关键字static

11.1.4自动变量

  默认情况下,在代码块或函数内的头部定义的任意变量都属于自动存储类。然而,也可以如下面所示的那样显式地使用关键字auto使您的这个意图更清晰:

int main(void)
{
    auto int plox;
}

  为了表明有意覆盖一个外部函数定义时,或者为了表明不能把变量改变为其他存储类这一点很重要时,可以这样做。关键字auto称为存储类说明符。

  代码块作用域和空链接意味着只有变量定义所在的代码块才可以通过名字访问该变量(当然,可以用参数向其他函数传递该变量的值和地址,但那是以间接的方式知道的)。另一个函数可以使用具有同样名字的变量,但那将是存储在不同内存位置中的一个独立变量。

  如果在内层代码块定义了一个具有和外层代码块变量同一名字的变量,将会发生什么?那么在内层代码块定义的名字是内层代码块所使用的变量。我们称之为内层定义覆盖了外部定义,但当运行离开内层代码块时,外部变量重新恢复作用。

一、不带{ }的代码块

  语句若为循环或者if语句的一部分,即使没有使用{ },也认为是一个代码块。更完整地说,整个循环是该循环所在代码块的子代码块,而循环体是整个循环代码块的子代码块。

●对C99的支持

  有些编译器可能不支持这些新的C99作用域规则。其他的编译器可能提供一个激活这些规则的选项。

二、自动变量的初始化

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

int main(void)
{
    int repid;//repid的初值是先前占用分配它的空间的任意值。
    int tents = 5;
}

11.1.5寄存器变量

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

int main(void)
{
    register int quick;
    ...
}

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

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

void mach(register int n)

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

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

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

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月25日07:45:41
//loc_stat.c--使用一个局部静态变量
#include <stdio.h>
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++);
}

结果:

Here comes iteration 1:
fade = 1 and stay = 1
Here comes iteration 2:
fade = 1 and stay = 2
Here comes iteration 3:
fade = 1 and stay = 3

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

int fade = 1;
static int stay = 1;

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

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

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

程序:

#define _CRT_SECURE_NO_WARNINGS 1
int Errupt; //外部定义的变量
double Up[100];  //外部定义的数组
extern char Coal;  //必须的声明,Coal在其他文件中定义
void next(void);
int main(void)
{
	extern int Errupt;  //可选的声明
	extern double Up[];  //可选的声明
	...
}
void next(void)
{
	...
}

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

extern int Errupt;

替换:

int Errupt;

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

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

以下3个例子展示了外部变量和自动变量的4种可能组合。

例1中有一个外部变量:Hocus。它对main()和magic()都是可见的。

//例1
int Hocus;
int magic();
int main(void)
{
    extern int Hocus;  //声明Hocus为外部变量
    ...
}
int magic()
{
    extern int Hocus;  //与上面的Hocus是同一变量
    ...
}

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

//例2
int Hocus;
int magic();
int main(void)
{
    extern int Hocus;  //声明Hocus为外部变量
    ...
}
int magic()
{
                       //未声明Hocus,但知道该变量
    ...
}

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

//例2
int Hocus;
int magic();
int main(void)
{
    extern int Hocus;  //声明Hocus为外部变量
    ...
}
int magic()
{
                       //未声明Hocus,但知道该变量
    ...
}

外部变量的作用域:从声明到文件结尾为止。

一、外部变量初始化

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

二、外部变量的使用

程序:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月25日08:41:55
//global.c -- 使用外部变量
#include <stdio.h>
int units = 0;  //一个外部变量
void critic(void);

int main(void)
{
	extern int units;  //可选的二次声明

	printf("How many pounds to a firkin of butter?\n");
	scanf("%d", &units);
	while (units != 56)
		critic();
	printf("You must have looked it up!\n");
	return 0;
}

void critic(void)
{
	//这里省略了可选的二次声明
	printf("No luck, chummy. Try again.\n");
	scanf("%d", &units);
}

结果:

How many pounds to a firkin of butter?
14
No luck, chummy. Try again.
56
You must have looked it up!

三、外部名字

  C99标准要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符。

四、定义和声明

例子:

int tern = 1;  //定义tern
int main(void)
{
    extern int tern;  //使用在其他地方定义的tern变量
}

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

如果这样做:

extern int tern;
int main(void)
{
   

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

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

extern char permis = 'Y';//错误

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

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

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

static int svil = 1;  //具有内部链接的静态变量
int main(void)
{  

  以前称这类变量为外部静态变量,但因为它们具有内部链接,因此会有些困惑。没有新的简称来代替外部静态一次,只能使用“具有内部链接的静态变量”。普通的外部变量可以被程序的任一文件中所包含的函数使用,而且具有内部链接的静态变量只可以被与它在同一文件中的函数使用。可以在函数中使用存储类说明符extern来再次声明任何具有文件作用域的变量。这样的声明并不改变链接。

int traveler = 1;  //外部链接
static int stayhome = 1;  //内部链接
int main(void)
{  
    extern int traveler;  //使用全局变量traveler
    extern int stayhome;  //使用全局变量stayhome

11.1.9多文件

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

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

11.2存储类说明符

  C语言中有5个作为存储类说明符的关键字,它们是auto、register、static、extern以及typedef。关键字typedef与内存存储无关,由于语法原因被归入此类。特别地,不可以在一个声明中使用以上存储类说明,这意味着不能将其他任一存储类说明符作为typedef的一部分。

  说明符auto表明一个变量具有自动存储时期。该说明符只能用在具有代码块作用域的变量声明中,而这样的变量已经拥有自动变量存储时期,因此它主要用来明确指出意图,使程序更易读。

  说明符register也只能用于具有代码块作用域的变量。它将一个变量归入寄存器存储类,这相当于请求将该变量存储在一个寄存器内,以更快地存取。它的使用无法获得变量的地址。

  说明符static在用于具有代码块作用域的变量的声明时,使该变量具有静态存储时期,从而得以在程序运行期间(即使在包含该变量的代码块并没有运行时)存在并保留其值。变量仍具有代码块作用域和空链接。static用于具有文件作用域的变量的声明时,表明该变量具有内部链接。

  说明符extern表明您在一个已经在别处定义了的变量。如果包含extern的声明具有文件作用域,所指向的变量必然具有外部链接。如果包含extern的声明具有代码块作用域,所指向的变量可能具有外部链接也可能具有内部链接,这取决于该变量的定义声明。

●总结:存储类

  自动变量具有代码块作用域、空链接和自动存储时期。它们是局部的,为定义它们的代码块(通常是一个函数)所私有。寄存器变量与自动变量具有相同的属性,但编译器可能使用速度更快的内存或寄存器来存储它们。无法获取一个寄存器变量的地址。

  具有静态存储时期的变量可能具有外部链接、内部链接或空链接。当变量在文件的所有函数之外声明时,它是一个具有文件作用域的外部变量,具有内部链接和静态存储时期。如果在这样的声明中再加上关键字static,将获得一个具有静态存储时期、文件作用域和内部链接的变量。如果在一个函数内使用关键字static声明变量,变量将具有静态存储时期、代码块作用域和空链接。

  当程序执行到包含变量声明的代码块时,给具有自动存储时期的变量分配内存,并在代码块结束时释放这部分内存。如果没有初始化,这样的变量具有一个无效值。在程序编译时给具有静态存储时期的变量分配内存,并且在程序运行时一直保持。如果没有初始化,这样的变量被设置为0。具有代码块作用域的变量局部于包含变量声明的代码块。

  具有文件作用域的变量对文件中在它的声明之后的所有函数可见。如果一个文件作用域变量具有外部链接,则它可被程序中的其他文件使用。如果一个文件作用域变量局有内部链接,他只能在声明它的文件中使用。

程序清单12.5 parta.c

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月30日08:16:45
//parta.c--各种存储类
#include <stdio.h>
void report_count();
void accumulate(int k);
int count =  0;  //文件作用域,外部链接
int main(void)
{
	int value;  //自动变量
	register int i;  //自动变量

	printf("Enter a postive integer(0 to quit): ");
	while (scanf("%d", &value) == 1 && value > 0)
	{
		++count;  //使用文件作用域
		for(i = value; i >= 0; i--)
			accumulate(i);
		printf("Enter a positive integer(0 to quit):");
	}
	report_count();

	return 0;
}

void report_count()
{
	printf("Loop executed %d times\n", count);
}

程序清单12.6 partb.c

#define _CRT_SECURE_NO_WARNINGS 1
//partb.c--程序的其余部分
#include <stdio.h>

extern int count;  //引用声明外部链接

static int total = 0;  //静态定义,内部链接
void accumulate(int k);  //原型
void accumulate(int k)  //k具有代码块作用域、空链接
{
	static int subtotal = 0;  //静态、空链接

	if (k <= 0)
	{
		printf("loop cycle: %d\n", count);
		printf("subtoatl: %d; total: %d\n", subtotal, total);
		subtotal = 0;
	}
	else
	{
		subtotal += k;
		total += k;
	}
}

结果:

Enter a postive integer(0 to quit): 5
loop cycle: 1
subtoatl: 15; total: 15
Enter a positive integer(0 to quit):10
loop cycle: 2
subtoatl: 55; total: 70
Enter a positive integer(0 to quit):2
loop cycle: 3
subtoatl: 3; total: 73
Enter a positive integer(0 to quit):0
Loop executed 3 times

12.3存储类和函数

  函数也有存储类。函数可以是外部的(默认情况下)或者静态的(C99增加了第三种可能性,即在“C预处理器和C库”中将讨论的内联函数)。外部函数可被其他文件中的函数调用,而静态函数只可以在定义它的文件中使用。通常用关键字extern来声明在其他文件中定义的函数。这一习惯做法主要是为了使程序更清晰,因为除非函数声明使用了关键字static,否则认为它是extern的。

使用哪种存储类

  保护型程序设计中一个非常重要的规则就是“需要知道”原则。尽可能保持每个函数的内部工作对该函数的私有性,只共享那些需要共享的变量。除了自动类型以外,其他类型也是有用的,并且可用。但请在使用一个类型前,问问自己是否必须那样做。

12.4随机数函数和静态变量

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月30日09:33:22
//diceroll.c--掷骰子的模拟程序
#include "diceroll.h"
#include <stdio.h>
#include <stdlib.h>  //为rand()函数提供类库


int roll_count = 0;  //外部链接

static int rollem(int sides)  //这个文件的私有函数
{
	int roll;

	roll = rand() % sides + 1;
	++roll_count;  //计数函数调用
	return roll;
}

int roll_n_dice(int dice, int sides)
{
	int d;
	int total = 0;
	if (sides < 2)
	{
		printf("Need at least 2 sides.\n");
		return -2;
	}
	if (dice < 1)
	{
		printf("Need at least 1 die.\n");
		return -1;
	}
	for(d = 0; d < dice; d++)
		total += rollem(sides);

	return total;
}
#define _CRT_SECURE_NO_WARNINGS 1
//manydice.c--多次掷骰子的模拟程序
//与diceroll.c一起编译
#include <stdio.h>  
#include <stdlib.h>  //为srand()函数提供原型
#include <time.h>  //为time()函数提供原型
#include "diceroll.h"  //为roll_n_dice()和roll_count函数提供原型

int main(void)
{
	int dice,roll;  //dice骰子个数,roll骰子点数和
	int sides;

	srand((unsigned int)time(0));  //随机化种子
	printf("Enter the number of sides per die, 0 to stop.\n");
	while (scanf("%d", &sides) == 1 && sides > 0)
	{
		printf("How many dice?\n");
		scanf("%d", &dice);
		roll = roll_n_dice(dice, sides);
		printf("You have rolled a %d using %d %d-sided dice.\n", roll, dice, sides);
		printf("How many sides? Enter 0 to stop.\n");
	}
	printf("The rollem() function was called %d times.\n", roll_count);
	printf("GOOD FORTUNE TO YOU!\n");

	return 0;
}
#pragma once
//diceroll.h
extern int roll_count;

int roll_n_dice(int dice, int sides);

  一个变量只可以有一个定义声明,但使用extern的声明是一个引用声明,这样的声明想用多少就可以用多少。因为程序使用srand()来随机确定随机数种子,所以大多数情况下,即使有相同的输入也不可能得到相同的输出。

12.6分配内存:malloc()和free()

  这5种存储类有一个共同之处:在决定了使用哪一存储类之后,就自动决定了作用域和存储时期。您的选择服从预先制定的内存管理规则。然而,还有另一个选择给您更多的灵活性。这一选择就是使用库函数来分配和管理内存。

  所有的程序都必须留出足够内存来存储它们使用的数据。一些内存分配是自动完成的。例如:

float x;
char place[] = "Dancing Oxen Creek";

  C可以在程序运行时分配更多的内存。主要工具是函数malloc(),它接受一个参数:所需内存字节数。然后malloc()找到可用内存中一个大小合适的块。内存时匿名的;也就是说,malloc()分配了内存,但没有为它指定名字。然而,它却可以返回那块内存第一个字节的地址。因此,您可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。因为char代表一个字节,所以传统上曾将malloc()定义为指向char的指针类型。然而,ANSI C标准使用了一个新类型:指向void的指针。这一类型被用作“通用指针”。函数malloc()可用来返回数组指针、结构指针等等,因此一般需要把返回值的类型指派为适当的类型。在ANSI C中,为了使程序清晰应对指针进行类型指派,但将void指针值赋值给其他类型的指针并不构成类型冲突。如果malloc()找不到所需的空间,它会返回空指针。

创建一个数组有三种方法:

●声明一个数组,声明时用常量表达式指定数组维数,然后可以用数组名访问数组元素。

●声明一个变长数组,声明时用常量表达式指定数组维数,然后用数组名来访问数组元素(回忆一下,这是C99的一个特性)

●声明一个指针,调用malloc(),然后使用该指针来访问数组元素。

  使用第二种或第三种方法可以做一些普通的数组声明做不到的事:创建一个动态数组,即一个在程序运行时才分配内存并可在程序运行时选择大小的数组。

  一般地,对应每个malloc()调用,应该调用一次free()。函数free()的参数是先前malloc()返回的地址,它释放先前分配的内存。这样,所分配的内存的持续时间从调用malloc()分配内存开始,到调用free()释放内存以供再使用为止。设想malloc()和free()管理着一个内存池。每次调用malloc()分配内存给程序使用,每次调用free()将内存归还到池中,使内存可被再次使用。free()的参数应是一指针,指向malloc()分配的内存块;不能使用free()来释放通过其他方式(例如声明一个数组)分配的内存。在头文件stdlib.h中有malloc()和free()的原型。

程序清单12.14 dyn_arr.c程序

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月31日07:09:11
//dyn_arr.c--为数组动态分配内存

#include <stdio.h>
#include <stdlib.h>  //为malloc()何free()函数提供原型


int main(void)
{
	double * ptd;
	int max;
	int number;
	int i = 0;

	puts("What is the maximum number of type double entries?");
	scanf("%d", &max);
	ptd = (double *)malloc(max * sizeof(double));
	if (ptd == NULL)
	{
		puts("Memory allocation failed. Goodbye.");
		exit(EXIT_FAILURE);
	}
	//ptd现在指向有max个元素的数组
	puts("Enter the values (q to quit): ");
	while(i < max && scanf("%lf", &ptd[i]) == 1)
		++i;
	printf("Here are your %d entries: \n", number = i);
	for (i = 0; i < number; i++)
	{
		printf("%7.2f ", ptd[i]);
		if(i % 7 == 6)
			putchar('\n');
	}
	if(i % 7 != 0)
		putchar('\n');
	puts("Done.");
	free(ptd);

	return 0;
}

结果:

What is the maximum number of type double entries?
5
Enter the values (q to quit):
20 30 35 25 40 80
Here are your 5 entries:
  20.00   30.00   35.00   25.00   40.00
Done.

  在C中,类型指派(double *)是可选的,而在C++中必须有,因此使用类型指派将使把C程序移植到C++更容易。

11.6.2函数calloc()

内存分配还可以用calloc()

long * newmen;
newmen = (long *)calloc(100, sizeof(long));

  与malloc()类似,calloc()在ANSI以前的版本中返回一个char指针,在ANSI中返回一个void指针。如果要存储不同类型,应使用类型指派运算符。这个新函数接受两个参数,都应是无符号的整数(在ANSI中是size_t类型)。第一个参数是内存单元的数量,第二个参数是每个单元以字节计的大小。函数free()也可以用来释放由calloc()分配的内存。

11.6.3动态内存分配与变长数组

  变长数组与malloc()在功能上有些一致。例如,它们都可以用来创建一个大小在运行时决定的数组:

int valmal()
{
    int n;
    int * pi;
    scanf("%d", &n);
    pi = (int *)malloc(n * sizeof(int));
    int ar[n];//变长数组
    pi[2] = ar[2] = -5;
    ...
}

  一个区别在于VLA是自动存储的。自动存储的结果之一就是VLA所使用内存空间在运行完定义部分之后就会自动释放。

11.6.4存储类与动态内存分配

  可以认为程序将它的可用内存分成了三个独立的部分:一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的;一个是自动变量的;另一个是动态分配的内存的。

  在编译时就已经知道静态存储时期存储类变量所需的内存数量,存储在这一部分的数据在整个程序运行期间都可用。这一类型的变量在程序执行开始时就已存在,到程序结束时终止。

  然而,一个自动变量在程序进入包含该变量定义的代码块时产生,在退出这一代码块时终止。因此,伴随着程序对函数的调用和终止,自动变量使用的内存数量也在增加和减少。典型地,将这一部分内存处理为一个堆栈。这意味着在内存中,新变量在创建时按顺序加入,在消亡时按相反顺序移除。

  动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。因此内存块可在一个函数中创建,而在另一个函数中释放。由于这点,动态内存分配所用的内存部分可能变成碎片状,也就是说,在活动的内存块之间散布着未使用的字节片。使用动态内存往往导致程序进程比使用堆栈内存慢。

11.7ANSI C的类型限定词

  C99授予类型限定词一个新属性:它们现在是幂等的!这听起来像是一个强大的功能,其实只意味着可以在一个声明中不止一次地使用同一限定词,多余的将被忽略掉:

const const const int n = 6; //相当于:const int n = 6;

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

下面的声明表示pf指向的值必须是不变的:

const float * pf;  //pf指向一个常量浮点数值

pf本身的值可以改变。

下面的声明表示指针pt本身的值不可以改变:

float * const pt;  //pt是一个常量指针

他必须总是指向同一个指针,但所指向的值可以改变。

下面的声明:

const float * const ptr;  

这意味着ptr必须总是指向同一个位置,并且它所指位置存储的值也不能改变。

这个新关键字的常见用法是声明作为函数形式参量参量的指针。例如,假定一个名为display()的函数显示一个数组的内容。为了使用它,您可能会把数组名作为实际参量传送,但数组名是一个地址,这样做将允许函数改变调用函数中的数据。下面的原型房防止了这样的情况发生:

void display(const array[], int limit);

二、对全局数据使用const

  在文件之间共享const数据时要小心。可以使用两个策略。首先是遵循外部变量的惯用规则:在一个文件中进行定义声明,在其他文件中进行引用声明(使用关键字extern)。其次是将常量放在一个include文件中。这时还必须使用静态外部存储类。

posted @   CodeMagicianT  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示