Tuesday, September 21, 2010

Storage Class Specifiers in C

There are four storage class specifiers supported by C:
extern
static
register
auto
These specifiers tell the compiler how to store the subsequent variable. The general
form of a declaration that uses one is shown here.
storage_specifier type var_name;
Notice that the storage specifier precedes the rest of the variable declaration.

extern

Because C/C++ allows separate modules of a large program to be separately compiled
and linked together, there must be some way of telling all the files about the global
variables required by the program. Although C technically allows you to define a
global variable more than once, it is not good practice (and may cause problems when
linking). More importantly, in C++, you may define a global variable only once. How,
then, do you inform all the files in your program about the global variables used by
the program?
The solution to the problem is found in the distinction between the declaration
and the definition of a variable. A declaration declares the name and type of a variable.

File One                   File Two
int x, y;                   extern int x, y;
char ch;                  extern char ch;
int main(void)           void func22(void)
{                               {
/* ... */                     x = y / 10;
}                              }
void func1(void)       void func23(void)
{                                {
x = 123;                        y = 10;
}                               }

Figure 2-1. Using global variables in separately compiled modules
A definition causes storage to be allocated for the variable. In most cases, variable
declarations are also definitions. However, by preceding a variable name with the
extern specifier, you can declare a variable without defining it. Thus, in a multifile
program, you can declare all of your global variables in one file and use extern
declarations in the other, as in Figure 2-1.
In File Two, the global variable list was copied from File One and the extern
specifier was added to the declarations. The extern specifier tells the compiler that the
variable types and names that follow it have been defined elsewhere. In other words,
extern lets the compiler know what the types and names are for these global variables
without actually creating storage for them again. When the linker links the two
modules, all references to the external variables are resolved.
The extern keyword has this general form:
extern var-list;
There is another, optional use of extern that you may occasionally see. When you
use a global variable inside a function, you can declare it as extern, as shown here:
int first, last; /* global definition of first
and last */
main(void)
{
extern int first; /* optional use of the
extern declaration */
.
.
.
}

static Variables
static variables are permanent variables within their own function or file. Unlike global
variables, they are not known outside their function or file, but they maintain their
values between calls. This feature makes them useful when you write generalized
functions and function libraries that other programmers may use. static has different
effects upon local variables and global variables.
static Local Variables
When you apply the static modifier to a local variable, the compiler creates permanent
storage for it, much as it creates storage for a global variable. The key difference
between a static local variable and a global variable is that the static local variable
remains known only to the block in which it is declared. In simple terms, a static
local variable is a local variable that retains its value between function calls.
static local variables are very important to the creation of stand-alone functions
because several types of routines must preserve a value between calls. If static variables
were not allowed, globals would have to be used, opening the door to possible side
effects. An example of a function that benefits from a static local variable is a numberseries
generator that produces a new value based on the previous one. You could use
a global variable to hold this value. However, each time the function is used in a
program, you would have to declare that global variable and make sure that it did not
conflict with any other global variables already in place. The better solution is to declare
the variable that holds the generated number to be static, as in this program fragment:
int series(void)
{
static int series_num;
series_num = series_num+23;
return series_num;
}
In this example, the variable series_num stays in existence between function calls,
instead of coming and going the way a normal local variable would. This means that
each call to series() can produce a new member in the series based on the preceding
number without declaring that variable globally.
You can give a static local variable an initialization value. This value is assigned
only once, at program start-up—not each time the block of code is entered, as with
normal local variables. For example, this version of series() initializes series_num
to 100:
int series(void)
{
static int series_num = 100;
series_num = series_num+23;
return series_num;
}
As the function now stands, the series will always begin with the value 123. While this
is acceptable for some applications, most series generators need to let the user specify
the starting point. One way to give series_num a user-specified value is to make it a
global variable and then let the user set its value. However, not defining series_num
as global was the point of making it static. This leads to the second use of static.
static Global Variables
Applying the specifier static to a global variable instructs the compiler to create a
global variable that is known only to the file in which you declared it. This means
that even though the variable is global, routines in other files may have no knowledge
of it or alter its contents directly, keeping it free from side effects. For the few situations
where a local static cannot do the job, you can create a small file that contains only the
functions that need the global static variable, separately compile that file, and use it
without fear of side effects.
To illustrate a global static, the series generator example from the previous section
is recoded so that a seed value initializes the series through a call to a second function
called series_start() . The entire file containing series() , series_start() , and series_num
is shown here:
/* This must all be in one file - preferably by itself. */
static int series_num;
void series_start(int seed);
int series(void);
int series(void)
{
series_num = series_num+23;
return series_num;
}
/* initialize series_num */
void series_start(int seed)
{
series_num = seed;
}
Calling series_start() with some known integer value initializes the series generator.
After that, calls to series() generate the next element in the series.
To review: The names of local static variables are known only to the block of code
in which they are declared; the names of global static variables are known only to the
file in which they reside. If you place the series() and series_start() functions in a
library, you can use the functions but cannot reference the variable series_num, which
is hidden from the rest of the code in your program. In fact, you can even declare and
use another variable called series_num in your program (in another file, of course). In
essence, the static modifier permits variables that are known only to the functions that
need them, without unwanted side effects.
static variables enable you to hide portions of your program from other portions.
This can be a tremendous advantage when you are trying to manage a very large and
complex program.
In C++, the preceding use of static is still supported, but deprecated. This means
that it is not recommended for new code. Instead, you should use a namespace,
which is described in Part Two.

register Variables
The register storage specifier originally applied only to variables of type int, char, or
pointer types. However, in Standard C, register's definition has been broadened so that
it applies to any type of variable.
Originally, the register specifier requested that the compiler keep the value of a
variable in a register of the CPU rather than in memory, where normal variables are stored. This meant that operations on a register variable could occur much faster than
on a normal variable because the register variable was actually held in the CPU and
did not require a memory access to determine or modify its value.
Today, the definition of register has been greatly expanded and it now may be
applied to any type of variable. Standard C simply states "that access to the object be
as fast as possible." (Standard C++ states that register is a "hint to the implementation
that the object so declared will be heavily used.") In practice, characters and integers
are still stored in registers in the CPU. Larger objects like arrays obviously cannot be
stored in a register, but they may still receive preferential treatment by the compiler.
Depending upon the implementation of the C/C++ compiler and its operating
environment, register variables may be handled in any way deemed fit by the
compiler's implementor. In fact, it is technically permissible for a compiler to ignore
the register specifier altogether and treat variables modified by it as if they weren't,
but this is seldom done in practice.
You can only apply the register specifier to local variables and to the formal
parameters in a function. Global register variables are not allowed. Here is an example
that uses register variables. This function computes the result of Me for integers:
int int_pwr(register int m, register int e)
{
register int temp;
temp = 1;
for(; e; e--) temp = temp * m;
return temp;
}
In this example, e, m, and temp are declared as register variables because they
are all used within the loop. The fact that register variables are optimized for speed
makes them ideal for control of or use in loops. Generally, register variables are used
where they will do the most good, which are often places where many references will
be made to the same variable. This is important because you can declare any number
of variables as being of type register, but not all will receive the same access speed
optimization.
The number of register variables optimized for speed allowed within any one code
block is determined by both the environment and the specific implementation of
C/C++. You don't have to worry about declaring too many register variables because
the compiler automatically transforms register variables into nonregister variables
when the limit is reached. (This ensures portability of code across a broad line of
processors.)
Usually at least two register variables of type char or int can actually be held in the
registers of the CPU. Because environments vary widely, consult your compiler's user
manual to determine if you can apply any other types of optimization options.
In C, you cannot find the address of a register variable using the & operator
(discussed later in this chapter). This makes sense because a register variable might be
stored in a register of the CPU, which is not usually addressable. But this restriction
does not apply to C++. However, taking the address of a register variable in C++ may
prevent it from being fully optimized.
Although the description of register has been broadened beyond its traditional
meaning, in practice it still generally has a significant effect only with integer and
character types. Thus, you should probably not count on substantial speed
improvements for other variable types.

No comments:

Post a Comment