Specifying
storage allocation
When
creating a variable, you have a number of options to specify the lifetime of
the variable, how the storage is allocated for that variable, and how the
variable is treated by the compiler.
Global
variables
Global
variables are defined outside all function bodies and are available to all
parts of the program (even code in other files). Global variables are
unaffected by scopes and are always available (i.e., the lifetime of a global
variable lasts until the program ends). If the existence of a global variable
in one file is declared using the
extern
keyword in another file, the data is available for use by the second file.
Here’s an example of the use of global variables:
//: C03:Global.cpp
//{L} Global2
// Demonstration of global variables
#include <iostream>
using namespace std;
int globe;
void func();
int main() {
globe = 12;
cout << globe << endl;
func(); // Modifies globe
cout << globe << endl;
} ///:~
Here’s
a file that accesses
globe
as an extern:
//: C03:Global2.cpp {O}
// Accessing external global variables
extern int globe;
// (The linker resolves the reference)
void func() {
globe = 47;
} ///:~
Storage
for the variable
globe
is created by the definition in
Global.cpp,
and that same variable is accessed by the code in
Global2.cpp.
Since the code in
Global2.cpp
is compiled separately from the code in
Global.cpp,
the compiler must be informed that the variable exists elsewhere by the
declaration
When
you run the program, you’ll see that the call to
func( )
does indeed affect the single global instance of
globe. In
Global.cpp,
you can see the special comment tag (which is my own design):
This
says that to create the final program, the object file with the name
Global2
must be linked in (there is no extension because the extension names of object
files differ from one system to the next). In
Global2.cpp,
the first line has another special comment tag
{O}
which says “don’t try to create an executable out of this file,
it’s being compiled so that it can be linked into some other
executable.” The
ExtractCode.cpp
program at the end of this book reads these tags and creates the appropriate
makefile
so everything compiles properly (you’ll learn about
makefiles
at the end of this chapter).
Local
variables
Local
variables occur within a scope; they are “local” to a function.
They are often called
automatic
variables because they automatically come into being when the scope is entered,
and automatically go away when the scope closes. The keyword
auto
makes this explicit, but local variables default to
auto
so it is never necessary to declare something as an
auto.
Register
variables
A
register variable is a type of local variable. The
register
keyword tells the compiler “make accesses to this variable as fast as
possible.” Increasing the access speed is implementation dependent but,
as the name suggests, it is often done by placing the variable in a register.
There is no guarantee that the variable will be placed in a register or even
that the access speed will increase. It is a hint to the compiler.
There
are restrictions to the use of
register
variables. You cannot take or compute the address of a
register
variable. A
register
variable can only be declared within a block (you cannot have global or
static
register
variables). You can, however, use a
register
variable as a formal argument in a function (i.e., in the argument list).
Generally,
you shouldn’t try to second-guess the compiler’s optimizer, since
it will probably do a better job than you can. Thus, the
register
keyword is best avoided.
static
The
static
keyword has several distinct meanings. Normally, variables defined local to a
function disappear at the end of the function scope. When you call the function
again, storage for the variables is created anew and the values are
re-initialized. If you want a value to be extant throughout the life of a
program, you can define a function’s local variable to be
static
and give it an initial value. The initialization is only performed the first
time the function is called, and the data retains its value between function
calls. This way, a function can “remember” some piece of
information between function calls.
You
may wonder why a global variable isn’t used instead. The beauty of a
static
variable is that it is unavailable outside the scope of the function, so it
can’t be inadvertently changed. This localizes errors.
Here’s
an example of the use of
static
variables:
//: C03:Static.cpp
// Using a static variable in a function
#include <iostream>
using namespace std;
void func() {
static int i = 0;
cout << "i = " << ++i << endl;
}
int main() {
for(int x = 0; x < 10; x++)
func();
} ///:~
Each
time
func( )
is called in the for loop, it prints a different value. If the keyword
static
is not used, the value printed will always be ‘1’.
The
second meaning of
static
is related to the first in the “unavailable outside a certain
scope” sense. When
static
is applied to a function name or to a variable that is outside of all
functions, it means “this name is unavailable outside of this
file.” The function name or variable is local to the file; we say it
has
file scope.
As a demonstration, compiling and linking the following two files will cause a
linker error:
//: C03:FileStatic.cpp
//{L} FileStatic2
// File scope demonstration. Compiling and
// linking this file with FileStatic2.cpp
// will cause a linker error
// File scope means only available in this file:
static int fs;
int main() {
fs = 1;
} ///:~
Even
though the variable
fs
is claimed to exist as an
extern
in the following file, the linker won’t find it because it has been
declared
static
in
FileStatic.cpp.
//: C03:FileStatic2.cpp {O}
// Trying to reference fs
extern int fs;
void func() {
fs = 100;
} ///:~
The
static
specifier may also be used inside a
class.
This explanation will be delayed until you learn to create classes, later in
the book.
extern
The
extern
keyword has already been briefly described and demonstrated. It tells the
compiler that a variable or a function exists, even if the compiler
hasn’t yet seen it in the file currently being compiled. This variable or
function may be defined in another file or further down in the current file. As
an example of the latter:
//: C03:Forward.cpp
// Forward function & data declarations
#include <iostream>
using namespace std;
// This is not actually external, but the
// compiler must be told it exists somewhere:
extern int i;
extern void func();
int main() {
i = 0;
func();
}
int i; // The data definition
void func() {
i++;
cout << i;
} ///:~
When
the compiler encounters the declaration ‘
extern
int i
’
it knows that the definition for
i
must exist somewhere as a global variable. When the compiler reaches the
definition of
i,
no other declaration is visible so it knows it has found the same
i
declared earlier in the file. If you were to define
i
as
static,
you would be telling the compiler that
i
is defined globally (via the
extern),
but it also has file scope
(via the
static),
so the compiler will generate an error.
Linkage
To
understand the behavior of C and C++ programs, you need to know about
linkage.
In an executing program, an identifier is represented by storage in memory that
holds a variable or a compiled function body. Linkage describes this storage it
is seen by the linker. There are two types of linkage:
internal
linkage
and
external
linkage. Internal
linkage means that storage is created to represent the identifier only for the
file being compiled. Other files may use the same identifier name with internal
linkage, or for a global variable, and no conflicts will be found by the linker
– separate storage is created for each identifier. Internal linkage is
specified by the keyword
static
in C and C++.
External
linkage means that a single piece of storage is created to represent the
identifier for all files being compiled. The storage is created once, and the
linker must resolve all other references to that storage. Global variables and
function names have external linkage. These are accessed from other files by
declaring them with the keyword
extern.
Variables defined outside all functions (with the exception of
const
in C++) and function definitions default to external linkage. You can
specifically force them to have internal linkage using the
static
keyword. You can explicitly state that an identifier has external linkage by
defining it with the
extern
keyword. Defining a variable or function with
extern
is not necessary in C, but it is sometimes necessary for
const
in C++.
Automatic
(local) variables exist only temporarily, on the stack, while a function is
being called. The linker doesn’t know about automatic variables,
and they have
no
linkage.
Constants
In
old (pre-Standard) C, if you wanted to make a constant, you had to use the
preprocessor: Everywhere
you used
PI,
the value 3.14159 was substituted by the preprocessor (you can still use this
method in C and C++).
When
you use the preprocessor to create constants, you place control of those
constants outside the scope of the compiler. No type checking
is performed on the name
PI
and you can’t take the address of
PI
(so you can’t pass a pointer
or a reference
to
PI).
PI
cannot be a variable of a user-defined type. The meaning of
PI
lasts from the point it is defined to the end of the file; the preprocessor
doesn’t recognize scoping.
C++
introduces the concept of a named constant
that is just like a variable, except its value cannot be changed. The modifier
const
tells the compiler that a name represents a constant. Any data type, built-in
or user-defined, may be defined as
const.
If you define something as
const
and then attempt to modify it, the compiler will generate an error.
You
must specify the type of a
const,
like this:
In
Standard C and C++, you can use a named constant in an argument list, even if
the argument it fills is a pointer or a reference (i.e., you can take the
address of a
const).
A
const
has a scope, just like a regular variable, so you can “hide” a
const
inside a function and be sure that the name will not affect the rest of the
program.
The
const
was taken from C++ and incorporated into Standard C, albeit quite differently.
In C, the compiler treats a
const
just like a variable that has a special tag attached that says
“don’t change me.” When you define a
const
in C, the compiler creates storage for it, so if you define more than one
const
with the same name in two different files (or put the definition in a header
file), the linker will generate error messages about conflicts. The intended
use of
const
in C is quite different from its intended use in C++ (in short, it’s
nicer in C++).
Constant
values
In
C++, a
const
must always have an initialization value (in C, this is not true). Constant
values for built-in types are expressed as decimal,
octal,
hexadecimal,
or floating-point numbers
(sadly, binary numbers were not considered important), or as characters. In
the absence of any other clues, the compiler assumes a constant value is a
decimal number. The numbers 47, 0 and 1101 are all treated as decimal numbers.
A
constant value with a leading 0 is treated as an octal number (base 8). Base 8
numbers can only contain digits 0-7; the compiler flags other digits as an
error. A legitimate octal number is 017 (15 in base 10).
A
constant value with a leading 0x is treated as a hexadecimal number (base 16).
Base 16 numbers contain the digits 0-9 and a-f or A-F. A legitimate hexadecimal
number is 0x1fe (510 in base 10).
Floating
point numbers can contain decimal points and exponential
powers (represented by e, which means “10 to the power”). Both the
decimal point and the
e
are optional. If you assign a constant to a floating-point variable, the
compiler will take the constant value and convert it to a floating-point number
(this process is one form of what’s called
implicit
type conversion).
However, it is a good idea to use either a decimal point or an
e
to remind the reader you are using a floating-point number; some older
compilers also need the hint.
Legitimate
floating-point constant values are: 1e4, 1.0001, 47.0, 0.0 and -1.159e-77. You
can add suffixes to force the type of floating-point number:
f
or
F
forces a
float,
L
or
l
forces a
long
double,
otherwise the number will be a
double. Character
constants are characters surrounded by single quotes, as: ‘
A’,
‘
0’,
‘ ‘. Notice there is a big difference between the character ‘
0’
(ASCII 96) and the value
0.
Special characters are represented with the “backslash escape”:
‘
\n’
(newline), ‘
\t’
(tab), ‘
\\’
(backslash), ‘
\r’
(carriage return), ‘
\"’
(double quotes), ‘
\'’
(single quote), etc. You can also express char constants in octal: ‘
\17’
or hexadecimal: ‘
\xff’.
volatile
Whereas
the qualifier
const
tells the compiler “this never changes” (which allows the compiler
to perform extra optimizations) the qualifier
volatile
tells the compiler “you never know when this will change,” and
prevents the compiler from performing any optimizations. Use this keyword when
you read some value outside the control of your code, such as a register in a
piece of communication hardware. A
volatile
variable is always read whenever its value is required, even if it was just
read the line before.
A
special case of some storage being “outside the control of your
code” is in a multithreaded program. If you’re watching a
particular flag that is modified by another thread or process, that flag should
be
volatile
so the compiler doesn’t make the assumption that it can optimize away
multiple reads of the flag.
Note
that
volatile
may have no effect when a compiler is not optimizing, but may prevent critical
bugs when you start optimizing the code (which is when the compiler will begin
looking for reduntant reads).
The
const
and
volatile
keywords
will be further illuminated in a later chapter.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru