12 存储类别、链接和内存管理

本文详细解读了C语言中存储类别,包括自动变量、寄存器变量、静态块作用域、静态外部链接与内部链接的区别,以及文件作用域、存储期和链接的概念。通过实例展示了如何声明和使用这些变量,以及链接外部变量和内存分配的技巧。
摘要由CSDN通过智能技术生成

1 存储类别

首先复习一些概念和术语。

在这里插入图片描述

从软件方面看,程序需要一种方法访问对象,这可以通过声明变量来完成:

int entity=3;

该声明创建了一个名为entity的标识符(identifier)。标识符是一个名称,在这种情况下,标识符可以用来指定(designate)特定对象的内容。标识符遵循变量的命名规则。在这个例子中,标识符entity即是C程序指定硬件内存中的对象的方式。该声明还提供了储存在对象中的值。

变量名不是指定对象的唯一途径。考虑下面的声明:

int * pt=&entity;
int ranks[10];

在这里插入图片描述

在这里插入图片描述

1.1 Scope

Scope describes the region or regions of a program that can access an identifier. A C variable has one of the following scopes: block scope, function scope, function prototype scope, or file scope . The program examples to date have used block scope almost exclusively for variables. A block is a region of code contained within an opening brace and the matching closing brace. For instance, the entire body of a function is a block. Any compound statement within a function also is a block. A variable defined inside a block has block scope, and it is visible from the point it is defined until the end of the block containing the definition. Also, formal function parameters, even though they occur before the opening brace of a function, have block scope and belong to the block containing the function body. Therefore, the variables cleo and patrick in the following code both have block scope extending to the closing brace:

double blocky(double cleo){
 	double patrick = 0.0;
 	...
 	return patrick;
}

Variables declared in an inner block have scope restricted just to that block:

double blocky(double cleo){
 	double patrick = 0.0;
 	int i;
 	for (i = 0; i < 10; i++){
 		double q = cleo * i; // start of scope for q
 		...
 		patrick *= q;
 	} // end of scope for q
 	...
 	return patrick;
} 

In this example, the scope of q is limited to the inner block, and only code within that block can access q .

Traditionally, variables with block scope had to be declared at the beginning of a block. C99 relaxed that rule, allowing you to declare variables anywhere in a block. One new possibility is in the control section of a for loop. That is, you now can do this:

for (int i = 0; i < 10; i++)
 	printf("A C99 feature: i = %d", i);

As part of this new feature, C99 expanded the concept of a block to include the code controlled by a for loop, while loop, do while loop, or if statement, even if no brackets are used. So in the previous for loop, the variable i is considered to be part of the for loop block. Therefore,
its scope is limited to the for loop. After execution leaves the for loop, the program will no longer see that i .

Function scope applies just to labels used with goto statements. This means that even if a label first appears inside an inner block in a function, its scope extends to the whole function. It would be confusing if you could use the same label inside two separate blocks, and function
scope for labels prevents this from happening.

Function prototype scope applies to variable names used in function prototypes, as in the following:

int mighty(int mouse, double large);

Function prototype scope runs from the point the variable is defined to the end of the prototype declaration. What this means is that all the compiler cares about when handling a function prototype argument is the types; the names you use, if any, normally don’t matter, and
they needn’t match the names you use in the function definition. One case in which the names matter a little is with variable-length array parameters:

void use_a_VLA(int n, int m, ar[n][m]);

If you use names in the brackets, they have to be names declared earlier in the prototype.

A variable with its definition placed outside of any function has file scope. A variable with file scope is visible from the point it is defined to the end of the file containing the definition. Take a look at this example:

#include <stdio.h>
int units = 0; 			/* a variable with file scope */

void critic(void);

int main(void){
 	...
}

void critic(void){
 	...
} 

Here, the variable units has file scope, and it can be used in both main() and critic() . Because they can be used in more than one function, file scope variables are also called global variables .

Note Translation Units and Files
What you view as several files may appear to the compiler as a single file. For example, suppose that, as often is the case, you include one or more header files ( .h extension) in a source code file ( .c sextension). A header file, in turn, may include other header files. So several
separate physical files may be involved. However, C preprocessing essentially replaces an
#include directive with the contents of the header file. Thus the compiler sees a single file
containing information from your source code file and all the header files. This single file is
called a translation unit. When we describe a variable as having file scope, it’s actually visible
to the whole translation unit. If your program consists of several source code files, then it will
consist of several translation units, with each translation unit corresponding to a source code
file and its included files.

1.2 Linkage

Next, let’s look at linkage. A C variable has one of the following linkages: external linkage, internal linkage, or no linkage . Variables with block scope, function scope, or function prototype scope have no linkage. That means they are private to the block, function, or prototype in which they are defined. A variable with file scope can have either internal or external linkage. A variable with external linkage can be used anywhere in a multifile program. A variable with internal linkage can be used anywhere in a single translation unit.

Note Formal and Informal Terms
The C Standard uses “file scope with internal linkage” to describe scope limited to one translation unit (a source code file plus its included header files) and “file scope with external linkage”
to describe scope that, at least potentially, extends to other translation units. But programmers
don’t always have the time or patience to use those terms. Some common short cuts are to
use “file scope” for “file scope with internal linkage” and “global scope” or “program scope”
for “file scope with external linkage.”

So how can you tell whether a file scope variable has internal or external linkage? You look to see if the storage class specifier static is used in the external definition:

int giants = 5; 			// file scope, external linkage
static int dodgers = 3; 	// file scope, internal linkage
int main(){
 	...
}
... 

The variable giants can be used by other files that are part of the same program. The dodgers variable is private to this particular file, but can be used by any function in the file.

1.3 Storage Duration

Scope and linkage describe the visibility of identifiers. Storage duration describes the persistence of the objects accessed by these identifiers. A C object has one of the following four storage durations: static storage duration, thread storage duration, automatic storage duration, or allocated storage duration

If an object has static storage duration, it exists throughout program execution. Variables with file scope have static storage duration. Note that for file scope variables, the keyword static indicates the linkage type, not the storage duration. A file scope variable declared using static has internal linkage,

but all file scope variables, using internal linkage or external linkage, have static storage duration.

  • 静态存储期:具有静态存储期的变量在程序的执行期间会一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字static声明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域便来给你都具有静态存储期。
  • 线程存储期:线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
  • 自动存储期:块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于存储下一个被调用函数的变量。变长数组稍有不同,它们的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾。

在下面的代码中,变量number和index在每次调用bore函数时被创建,在离开函数时被销毁:

void bore(int number){
 	int index;
 	for (index = 0; index < number; index++)
 		puts("They don't make them the way they used to.\n");
 	return 0;
} 

然而,块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前面加上关键字static:

void more(int number){
 	int index;
 	static int ct = 0;
 	...
 	return 0;
} 

这样,变量ct储存在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在more函数块中。只有在执行该程序时,程序才能使用ct访问它所指定的对象(但是,该函数可以给其他函数提供该存储区的地址以便间接访问该对象,例如通过指针形参或返回值)。

C使用作用域、链接和存储期变量定义了多种存储方案。已分配存储期在本节后面介绍。因此,剩下5种存储类别:自动、寄存器、静态块作用域、静态外部链接、静态内部链接,如表12.1所列。

在这里插入图片描述

1.4 自动变量

属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。为了更清楚地表达你的意图(例如,为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别),可以显式使用关键字auto,如下所示:

int main(void){
 	auto int plox; 

关键字auto是存储类别说明符(storage-class specifier)。(使用auto时要注意,auto关键字在C++中的用法完全不同)。

块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量。另一个函数可以使用同名变量,但是该变量是存储在不同内存位置上的另一个变量。
在这里插入图片描述

int loop(int n){
 	int m; // m in scope
 	scanf("%d", &m);
 	{
 		int i; // both m and i in scope
 		for (i = m; i < n; i++)
 			puts("i is local to a sub-block\n");
 	}
 	return m; // m in scope, i gone
} 

在这里插入图片描述
程序12.1 hiding.c

// hiding.c -- variables in blocks
#include <stdio.h>

int main(){
 	int x = 30; // original x
 	
 	printf("x in outer block: %d at %p\n", x, &x);
 	{
 		int x = 77; // new x, hides first x
 		printf("x in inner block: %d at %p\n", x, &x);
 	}
 	
 	printf("x in outer block: %d at %p\n", x, &x);
 	while (x++ < 33){ // original x
 		int x = 100; // new x, hides first x
 		x++;
 		printf("x in while loop: %d at %p\n", x, &x);
 	}
 	printf("x in outer block: %d at %p\n", x, &x);
 	return 0;
} 

下面是该程序的输出:

x in outer block: 30 at 0x7fff5fbff8c8
x in inner block: 77 at 0x7fff5fbff8c4
x in outer block: 30 at 0x7fff5fbff8c8
x in while loop: 101 at 0x7fff5fbff8c0
x in while loop: 101 at 0x7fff5fbff8c0
x in while loop: 101 at 0x7fff5fbff8c0
x in outer block: 34 at 0x7fff5fbff8c8 

在这里插入图片描述

1.4.1 没有花括号的块

在这里插入图片描述
待补充 399

1.5 寄存器变量

在这里插入图片描述

在这里插入图片描述

1.6 Static Variables with Block Scope

The name static variable sounds like a contradiction, like a variable that can’t vary. Actually, static means that the variable stays put in memory, not necessarily in value. Variables with file scope automatically (and necessarily) have static storage duration. As mentioned earlier, you also can create local variables having block scope but static duration. These variables have the same scope as automatic variables, but they don’t vanish when the containing function
ends its job. That is, such variables have block scope, no linkage, but static storage duration. The computer remembers their values from one function call to the next—such variables are created by declaring them in a block (which provides the block scope and lack of linkage) with
the storage-class specifier static (which provides the static storage duration). The example in Listing 12.3illustrates this technique.

Listing 12.3 The loc_stat.c Program

/* loc_stat.c -- using a local static variable */
#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++);
}

Running the program returns this output:

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 

The static variable stay remembers that its value was increased by 1, but the fade variable starts anew each time. This points out a difference in initialization: fade is initialized each time trystat() is called, but stay is initialized just once, when trystat() is compiled. Static variables are initialized to zero if you don’t explicitly initialize them to some other value.

The two declarations look similar:

int fade = 1;
static int stay = 1; 

However, the first statement is really part of the trystat() function and is executed each time the function is called. It is a runtime action. The second statement isn’t actually part of the trystat() function. If you use a debugger to execute the program step-by-step, you’ll see that the program seems to skip that step. That’s because static variables and external variables are already in place after a program is loaded into memory. Placing the statement in the trystat() function tells the compiler that only the trystat() function is allowed to see the variable; it’s not a statement that’s executed during runtime.

You can’t use static for function parameters:

int wontwork(static int flu);	//	not allowed 

Another term for a static variable with block scope is a “local static variable.” Also, if you read some of the older C literature, you’ll find this storage class referred to as the internal static storage class. However, the word internal was used to indicate internal to a function, not internal linkage.

1.7 Static Variables with External Linkage

A static variable with external linkage has file scope, external linkage, and static storage duration. This class is sometimes termed the external storage class, and variables of this type are
called external variables. You create an external variable by placing a defining declaration outside of any function. As a matter of documentation, an external variable can additionally be declared inside a function that uses it by using the extern keyword. If a particular external variable is defined in one source code file and is used in a second source code file, declaring the variable in the second file with extern is mandatory. Declarations look like this:

int Errupt; 			/* externally defined variable */
double Up[100]; 		/* externally defined array */
extern char Coal; 		/* mandatory declaration if */
 						/* Coal defined in another file */

void next(void);
 
int main(void){
 	extern int Errupt; 	/* optional declaration */
 	extern double Up[]; /* optional declaration */
 	...
}
 
void next(void){
 	...
} 

Note that you don’t have to give the array size in the optional declaration of double Up . That’s because the original declaration already supplied that information. The group of extern declarations inside main() can be omitted entirely because external variables have file scope, so they are known from the point of declaration to the end of the file. They do serve, however, to document your intention that main() use these variables.

If only extern is omitted from the declaration inside a function, a separate automatic variable is set up. That is, replacing

extern int Errupt;

with

int Errupt;

in main() causes the compiler to create an automatic variable named Errupt. It would be a separate, local variable, distinct from the original Errupt. The local variable would be in scope while the program executes main(), but the external Errupt would be in scope for other functions, such as next(), in the same file. In short, a variable in block scope “hides” a variable of the same name in file scope while the program executes statements in the block. If, for some improbable reason, you actually need to use a local variable with the same name as a global variable, you might opt to use the auto storage-specifier in the local declaration to document your choice.

External variables have static storage duration. Therefore, the array Up maintains its existence and values regardless of whether the program is executing main(), next(), or some other function.

The following three examples show four possible combinations of external and automatic variables. Example 1 contains one external variable: Hocus. It is known to both main() and magic() .

/* Example 1 */
int Hocus;
int magic();
int main(void){
 	extern int Hocus; // Hocus declared external
 	...
}

int magic(){
 	extern int Hocus; // same Hocus as above
 	...
} 

Example 2 has one external variable, Hocus, known to both functions. This time, magic() knows it by default.

/* Example 2 */
int Hocus;
int magic();

int main(void){
 	extern int Hocus; // Hocus declared external
 	...
}

int magic(){
 	// Hocus not declared but is known
 	...
} 

In Example 3, four separate variables are created. The Hocus variable in main() is automatic by default and is local to main. The Hocus variable in magic() is automatic explicitly and is known only to magic(). The external Hocus variable is not known to main() or magic() but would be known to any other function in the file that did not have its own local Hocus . Finally, Pocus is an external variable known to magic() but not to main() because Pocus follows main() .

/* Example 3 */
int Hocus;
int magic();

int main(void){
 	int Hocus; 			// Hocus declared, is auto by default
 	...
}

int Pocus;
int magic(){
 	auto int Hocus; 	// local Hocus declared automatic
 	...
} 

These examples illustrate the scope of external variables: from the point of declaration to the end of the file. They also illustrate the lifetimes of variables. The external Hocus and Pocus variables persist as long as the program runs, and, because they aren’t confined to any one function, they don’t fade away when a particular function returns.

1.7.1 初始化外部变量

外部变量和自动变量类似,也可以被显式初始化,与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为0。这一原则也适用于外部定义的数组元素。与自动变量的情况不同,只能使用常量表达式初始化文件作用域变量:

int x = 10; 				// ok, 10 is constant
int y = 3 + 20; 			// ok, a constant expression
size_t z = sizeof(int); 	// 没问题,用于初始化的是常量表达式
int x2 = 2 * x; 			// 不行,x是常量

(只要不是变长数组,sizeof表达式可被视为常量表达式)

1.7.2 使用外部变量

下面给出一个使用外部变量的例子。假设有两个函数main和critic,它们都要访问变量units。可以把units声明在这两个函数的上面,如程序12.4所示:

程序12.4 global.c

/* global.c -- uses an external variable */
#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){
 	/* optional redeclaration omitted */
 	printf("No luck, my friend. Try again.\n");
 	scanf("%d", &units);
}

下面是程序的一个输出示例:

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

在这里插入图片描述

1.7.3 外部名称

在这里插入图片描述

1.7.4 定义和声明

下面进一步介绍定义变量和声明变量的区别。考虑下面的例子:

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。

1.8 内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。在所有函数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有这种存储类别:

static int svil = 1; // static variable, internal linkage
int main(void)
{ 

在这里插入图片描述

1.9 多文件

在这里插入图片描述

1.10 存储类别说明符

关键字static和extern的含义取决于上下文。C语言有6个关键字作为存储类别说明符:auto、register、static和extern、_Thread_local和typedef。 typedef关键字与任何内存存储无关,把它归于此类有一些语法上的原因。尤其是,在绝大多数情况下,不能在声明中使用多个存储类型说明符,所以这意味着不能使用多个存储类别说明符作为typedef的一部分。唯一例外的是_Thread_local,它可以和static或extern一起使用。

  • auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块

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

前面讨论的存储类别有一个共同之处:在确定用哪种存储类别后,根据已制定好的内存管理规则,将自动选择其作用域和存储期。然而,还有更灵活的选择,即用库函数分配和管理内存。

首先回顾一下内存分配。所有程序都必须预留足够的内存来储存程序使用的数据。这些内存中有些是自动分配的。例如,以下声明:

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

为一个float类型的值和一个字符串预留了足够的内存,或者可以显式指定分配一定数量的内存:

int plates[100]; 

该声明预留了100个内存位置,每个位置都用于储存int类型的值。声明还为内存提供了一个标识符。因此,可以使用x或place识别数据。

另外一种分配内存的方式是通过malloc()函数在运行时分配内存,malloc函数接受一个参数:所需的字节数。malloc()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说,malloc函数分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。

由于char表示1字节,malloc()的返回类型通常被定义为指向char的指针。然而,从ANSI C标准开始,C使用一个新的类型:指向void的指针。该类型相当于一个“通用指针”。malloc函数可以用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。在ANSI C中,应该坚持使用强制类型转换,提高代码的可读性。然而,把指向void的指针赋给任意类型的指针完全不用考虑类型匹配的问题。如果malloc()分配内存失败,将返回空指针。

我们试着用malloc创建一个数组。除了用malloc在程序运行时请求一块内存,还需要一个指针记录这块内存的位置。例如,考虑下面的代码:

double * ptd;
ptd = (double *) malloc(30 * sizeof(double)); 

以上代码为30个double类型的值请求内存空间,并设置ptd指向该位置。接下来我们可以使用表达式ptd[0]访问该块的首元素,ptd[1]访问第二个元素,以此类推。

此时我们就有了三种创建数组的方式:

  • 声明数组时,用常量表达式表示数组的维度,用数组名访问数组的元素。可以用静态内存或自动内存创建这种数组
  • 声明变长数组(C99新增的特性)时,用常量表达式表示数组的维度,用数组名访问数组的元素。具有这种特性的数组只能在自动内存中创建。
  • 声明一个指针,调用malloc,将其返回值赋给指针,使用指针访问数组的元素。该指针可以是静态的或自动的。

在这里插入图片描述
在这里插入图片描述
程序12.14 dyn_arr.c

/* dyn_arr.c -- dynamically allocated array */
#include <stdio.h>
#include <stdlib.h> /* for malloc(), free() */
 
int main(void) {
 	double * ptd;
 	int max = 0;
 	int number;
 	int i = 0;

 	puts("What is the maximum number of type double entries?");
 	if (scanf("%d", &max) != 1){
 		puts("Number not correctly entered -- bye.");
 		exit(EXIT_FAILURE);
	}
 	ptd = (double *) malloc(max * sizeof (double));
 	if (ptd == NULL){
 		puts("Memory allocation failed. Goodbye.");
 		exit(EXIT_FAILURE);
	}
 	/* ptd now points to an array of max elements */
 	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;
}

下面是程序的运行结果,程序通过交互的方法首先让用户确定数组大小,我们设置数组大小为5。后来虽然输入了6个数字,但程序只处理前5个数。

What is the maximum number of 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. 

在这里插入图片描述
在这里插入图片描述

4.1 free()的重要性

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4.2 calloc()函数

在这里插入图片描述

4.3 动态内存分配和变长数组

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.4 存储类别和动态内存分配

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 ANSI C Type Qualifiers

You’ve seen that a variable is characterized by both its type and its storage class. C90 added two more properties: constancy and volatility. These properties are declared with the keywords const and volatile, which create qualified types. The C99 standard added a third qualifier, restrict, designed to facilitate compiler optimizations. And C11 adds a fourth, _Atomic. C11 provides an optional library, managed by stdatomic.h, to support concurrent programming, and _Atomic is part of that optional support.

C99 granted type qualifiers a new property—they now are idempotent! Although this sounds like a powerful claim, all it really means is that you can use the same qualifier more than once in a declaration, and the superfluous ones are ignored:

const const const int n=6;	//same as const int n = 6;

This makes it possible, for example, for the following sequence to be accepted:

typedef const int zip;
const zip q=8;
5.1 The const Type Qualifier

待补充 第十节中的const内容。

Chapter 4 , “Character Strings and Formatted Input/Output” and Chapter 10 , “Arrays and Pointers” have already introduced const. To review, the const keyword in a declaration establishes a variable whose value cannot be modified by assignment or by incrementing or decrementing. On an ANSI-compliant compiler, the code

const int nochange; 	/* qualifies m as being constant */			
nochange = 12; 			/* not allowed */ 		

should produce an error message. You can, however, initialize a const variable. Therefore, the following code is fine:

const int nochange = 12; 

The preceding declaration makes nochange a read-only variable. After it is initialized, it cannot be changed.

You can use the const keyword to, for example, create an array of data that the program can’t alter:

const int days1[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
5.1.1 Using const with Pointers and Parameter Declarations

Using the const keyword when declaring a simple variable and an array is pretty easy. Pointers
are more complicated because you have to distinguish between making the pointer itself const
and making the value that is pointed to const. The declaration

const float * pf; 	/* pf points to a constant float value */

establishes that pf points to a value that must remain constant. The value of pf itself can
be changed. For example, it can be set to point at another const value. In contrast, the
declaration

float * const pt; 	/* pt is a const pointer */

says that the pointer pt itself cannot have its value changed. It must always point to the same
address, but the pointed-to value can change. Finally, the declaration

const float * const ptr;

means both that ptr must always point to the same location and that the value stored at the
location must not change.

There is a third location in which you can place const :

float const * pfc;		// same as const float * pfc;

As the comment indicates, placing const after the type name and before the * means that the pointer can’t be used to change the pointed-to value. In short, a const anywhere to the left of the * makes the data constant; and a const to the right of the * makes the pointer itself constant.

One common use for this new keyword is declaring pointers that serve as formal function parameters. For example, suppose you have a function called display() that displays the contents of an array. To use it, you would pass the name of the array as an actual argument, but the name of an array is an address. That would enable the function to alter data in the calling function. But the following prototype prevents this from happening:

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

In a prototype and a function header, the parameter declaration const int array[] is the same as const int * array, so the declaration says that the data to which array points cannot be changed.

The ANSI C library follows this practice. If a pointer is used only to give a function access to values, the pointer is declared as a pointer to a const-qualified type. If the pointer is used to alter data in the calling function, the const keyword isn’t used. For example, the ANSI C declaration for strcat() is this:

char *strcat(char * restrict s1, const char * restrict s2);

Recall that strcat() adds a copy of the second string to the end of the first string. This modifies the first string, but leaves the second string unchanged. The declaration reflects this.

5.1.2 Using const with Global Data

Recall that using global variables is considered a risky approach because it exposes data to being mistakenly altered by any part of a program. That risk disappears if the data is constant, so it is perfectly reasonable to use global variables with the const qualifier. You can have const variables, const arrays, and const structures.

One area that requires care, however, is sharing const data across files. There are two strategies you can use. The first is to follow the usual rules for external variables—use defining declarations in one file and reference declarations (using the keyword extern) in the other files:

/* file1.c -- defines some global constants */
const double PI = 3.14159;
const char * MONTHS[12] =
	{"January", "February", "March", "April", "May", "June", "July",
	 "August", "September", "October", "November", "December"};
	 
/* file2.c -- use global constants defined elsewhere */
extern const double PI;
extern const * MONTHS[];

The second approach is to place the constants in an include file. Here, you must take the additional step of using the static external storage class:

/* constant.h -- defines some global constants */
static const double PI = 3.14159;
static const char * MONTHS[12] =
	{"January", "February", "March", "April", "May", "June", "July",
 	 "August", "September", "October", "November", "December"};
 	 
/* file1.c -- use global constants defined elsewhere */
#include "constant.h"

/* file2.c -- use global constants defined elsewhere */
#include "constant.h" 

If you don’t use the keyword static, including constant.h in file1.c and in file2.c would result in each file having a defining declaration of the same identifier, which is not supported by the C standard. (Some compilers, however, do allow it.) By making each identifier static external, you actually give each file a separate copy of the data. That wouldn’t work if the files are supposed to use the data to communicate with one another because each file would see
only its own copy. Because the data is constant (by using the const keyword) and identical (by having both files include the same header file), however, that’s not a problem.

The advantage of the header file approach is that you don’t have to remember to use defining declarations in one file and reference declarations in the next; all files simply include the same header file. The disadvantage is that the data is duplicated. For the preceding examples, that’s not a real problem, but it might be one if your constant data includes enormous arrays.

5.2 The volatile Type Qualifier
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值