Inapoi
Inainte
Cuprins
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
extern int globe;
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):
//{L} Global2
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 in Volume
2 of this book (downloadable at www.BruceEckel.com) 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 be declared only 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).
In general, 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 performed only 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
// 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 as 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 so these have no
linkage.
Constants
In old (pre-Standard) C, if you wanted to
make a constant, you had to use the
preprocessor:
#define PI 3.14159
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 that 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:
const int x = 10;
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 contain only 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 of”). 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 that
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 based on the stability of that variable. 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
redundant reads).
 |
|