The Storage Class Specifiers
In C, the storage class of a variable refers to the combination of its spatial and temporal regions.
You've learned about scope, which specifies the spatial region of a variable. Now, let's focus on duration, which indicates the temporal region of a variable.
There are four specifiers and two modifiers that can be used to indicate the duration of a variable. These specifiers and modifiers are introduced in the following sections.
The auto Specifier
The auto specifier indicates that the memory location of a variable is temporary. In other words, a variable's reserved space in the memory can be erased or relocated when the variable is out of its scope.
Only variables with block scope can be declared with the auto specifier. The auto keyword is rarely used, however, because the duration of a variable with block scope is temporary by default.
The static Specifier
The static specifier, on the other hand, can be applied to variables with either block scope or program scope. When a variable within a function is declared with the static specifier, the variable has a permanent duration. In other words, the memory storage allocated for the variable is not destroyed when the scope of the variable is exited, the value of the variable is maintained outside the scope, and if execution ever returns to the scope of the variable, the last value stored in the variable is still there.
For instance, in the following code portion:
int main()
{
int i; /* block scope and temporary duration */
static int j; /* block scope and permanent duration */
.
.
return 0;
}
the integer variable i has temporary duration by default. But the other integer variable, j, has permanent duration due to the storage class specifier static.
The program in Listing 14.3 shows the effect of the static specifier on variables.
TYPE
Listing 14.3. Using the static specifier.
1: /* 14L03.c: Using the static specifier */
2: #include <stdio.h>
3: /* the add_two function */
4: int add_two(int x, int y)
5: {
6: static int counter = 1;
7:
8: printf("This is the function call of %d,\n", counter++);
9: return (x + y);
10: }
11: /* the main function */
12: main()
13: {
14: int i, j;
15:
16: for (i=0, j=5; i<5; i++, j--)
17: printf("the addition of %d and %d is %d.\n\n",
18: i, j, add_two(i, j));
19: return 0;
20: }
The following output is displayed on the screen after the executable (14L03.exe) is run from a DOS prompt:
OUTPUT
C:\app>14L03
This is the function call of 1,
the addition of 0 and 5 is 5.
This is the function call of 2,
the addition of 1 and 4 is 5.
This is the function call of 3,
the addition of 2 and 3 is 5.
This is the function call of 4,
the addition of 3 and 2 is 5.
This is the function call of 5,
the addition of 4 and 1 is 5.
C:\app>
ANALYSIS
The purpose of the program in Listing 14.3 is to call a function to add two integers and then print out the result returned by the function on the screen. The function is called several times. A counter is set to keep track of how many times the function has been called.
This function, called add_two(), is declared in lines 4_10. There are two int arguments, x and y, that are passed to the function, and the addition of the two arguments is returned in line 9. Note that there is an integer variable, counter, that is declared with the static specifier in line 6. Values stored by counter are retained because the duration of the variable is permanent. In other words, although the scope of counter is within the block of the add_two() function, the memory location of counter and value saved in the location are not changed after the add_two() function is called and the execution control is returned back to the main() function.
Therefore, the counter variable is used as a counter to keep the number of calls received by the add_two() function. In fact, the statement of the printf() function in line 8 prints out the value saved by the counter variable each time the add_two() function is called. In addition, counter is incremented by one each time after the printf() function is executed.
The for loop, declared in lines 16_18 within the main() function, calls the add_two() function five times. The values of the two integer variables, i and j, are passed to the add_two() function for the operation of addition. Then, the return value from the add_two() function is displayed on the screen by the printf() function in lines 17 and 18.
From the output, you can see that the value saved by counter is indeed incremented by one each time the add_two() function is called, and is retained after the function exits because the integer variable counter is declared with static. Note that counter is only initialized once when the add_two() function is called for the first time.
File Scope and the Hierarchy of Scopes
In the first part of this hour, I mentioned three of the four types of scopes: block scope, function scope, and program scope. It's time now to introduce the fourth scope—file scope.
In C, a global variable declared with the static specifier is said to have file scope. A variable with file scope is visible from its declaration point to the end of the file. Here the file refers to the program file that contains the source code. Most large programs consist of several program files.
The following portion of source code shows variables with file scope:
int x = 0; /* program scope */
static int y = 0; /* file scope */
static float z = 0.0; /* file scope */
int main()
{
int i; /* block scope */
.
.
.
return 0;
}
Here the int variable y and the float variable z both have file scope.
Figure 14.1 shows the hierarchy of the four scopes. As you can see, a variable with block scope is the most limited and is not visible outside the block within which the variable is
Figure 14.1. The hierarchy of the four scopes.
declared. On the other hand, a variable with program scope is visible within all files, functions, and other blocks that make up the program.
The register Specifier
The word register is borrowed from the world of computer hardware. Each computer has a certain number of registers to hold data and perform arithmetic or logical calculations. Because registers are located within the CPU (central processing unit) chip, it's much quicker to access a register than a memory location. Therefore, storing variables in registers may help to speed up your program.
The C language provides you with the register specifier. You can apply this specifier to variables when you think it's necessary to put the variables into the computer registers.
However, the register specifier only gives the compiler a suggestion. In other words, a variable specified by the register keyword is not guaranteed to be stored in a register. The compiler can ignore the suggestion if there is no register available, or if some other restrictions have to apply.
It's illegal to take the address of a variable that is declared with the register specifier because the variable is intended to be stored in a register, not in the memory.
In the following portion of code, the integer variable i is declared with the register specifier:
int main()
{
/* block scope with the register specifier */
register int i;
. . .
for (i=0; i<MAX_NUM; i++){
/* some statements */
}
. . .
return 0;
}
The declaration of i suggests that the compiler store the variable in a register. Because i is intensively used in the for loop, storing i in a register may increase the speed of the code shown here.
The extern Specifier
As stated in the section titled "Program Scope," a variable with program scope is visible through all source files that make up an executable program. A variable with program scope is also called a global variable.
Here is a question: How can a global variable declared in file A, for instance, be seen in file B? In other words, how does the compiler know that the variable used in file B is actually the same variable declared in file A?
The answer is: Use the extern specifier provided by the C language to allude to a global variable defined elsewhere. In this case, we declare a global variable in file A, and then declare the variable again using the extern specifier in file B.
For instance, suppose you have two global int variables, y and z, that are defined in one file, and then, in another file, you may have the following declarations:
int x = 0; /* a global variable */
extern int y; /* an allusion to a global variable y */
int main()
{
extern int z; /* an allusion to a global variable z */
int i; /* a local variable */
.
.
.
return 0;
}
As you can see, there are two integer variables, y and z, that are declared with the extern specifier, outside and inside the main() function, respectively. When the compiler sees the two declarations, it knows that the declarations are actually allusions to the global variables y and z that are defined elsewhere.
NOTE
To make your program portable across different computer platforms, you can apply the following rules in your program when you declare or allude to global variables:
You can ignore the extern specifier, but include an initializer, when you declare a global variable.
You should use the extern specifier (without an initializer) when you allude to a global variable defined elsewhere.
The Storage Class Modifiers
Besides the four storage class specifiers introduced in the previous sections, C also provides you with two storage class modifiers (or qualifiers, as they're sometimes called) that you can use to indicate to the C compiler how variables may be accessed.
The const Modifier
If you declare a variable with the const modifier, the content of the variable cannot be changed after it is initialized.
For instance, the following expression indicates to the compiler that circle_ratio is a variable whose value should not be changed:
const double circle_ratio = 3.141593;
Likewise, the value of the character array str declared in the following statement cannot be changed, either:
const char str[] = "A string constant";
Therefore, it's illegal to do something like this:
str[0] = `a'; /* It's not allowed here. */
In addition, you can declare a pointer variable with the const modifier so that an object pointed to by the pointer cannot be changed. For example, consider the following pointer declaration with the const modifier:
char const *ptr_str = "A string constant";
After the initialization, you cannot change the content of the string pointed to by the pointer ptr_str. For instance, the following statement is not allowed:
*ptr_str = `a'; /* It's not allowed here. */
However, the ptr_str pointer itself can be assigned a different address of a string that is declared with char const.
The volatile Modifier
Sometimes, you want to declare a variable whose value can be changed without any explicit assignment statement in your program. For instance, you might declare a global variable that contains characters entered by the user. The address of the variable is passed to a device register that accepts characters from the keyboard. However, when the C compiler optimizes your program automatically, it intends to not update the value held by the variable unless the variable is on the left side of an assignment operator (=). In other words, the value of the variable is likely not changed even though the user is typing in characters from the keyboard.
To ask the compiler to turn off certain optimizations on a variable, you can declare the variable with the volatile specifier. For instance, in the following code portion, a variable, keyboard_ch, declared with the volatile specifier, tells the compiler not to optimize any expressions of the variable because the value saved by the variable may be changed without execution of any explicit assignment statement:
void read_keyboard()
{
volatile char keyboard_ch; /* a volatile variable */
.
.
.
}
- 9 views