To allow programs to be written in logical parts, C++ supports what is commonly known as separate compilation. Separate compilation lets us split our programs into several files, each of which can be compiled independently.
When we separate a program into multiple files, we need a way to share code across those files. For example, code defined in one file may need to use a variable defined in another file. As a concrete example, consider std::cout
and std::cin
. These are objects defined somewhere in the standard library, yet our programs can use these objects.
Caution: Uninitialized Variables Cause Run-Time Problems
An uninitialized variable has an indeterminate value. Trying to use the value of an uninitialized variable is an error that is often hard to debug. Moreover, the compiler is not required to detect such errors, although most will warn about at least some uses of uninitialized variables.
What happens when we use an uninitialized variable is undefined. Sometimes, we’re lucky and our program crashes as soon as we access the object. Once we track down the location of the crash, it is usually easy to see that the variable was not properly initialized. Other times, the program completes but produces erroneous results. Even worse, the results may appear correct on one run of our program but fail on a subsequent run. Moreover, adding code to the program in an unrelated location can cause what we thought was a correct program to start producing incorrect results.
We recommend initializing every object of built-in type. It is not always necessary, but it is easier and safer to provide an initializer until you can be certain it is safe to omit the initializer.
To support separate compilation, C++ distinguishes between declarations and definitions. A declaration makes a name known to the program. A file that wants to use a name defined elsewhere includes a declaration for that name. A definition creates the associated entity.
A variable declaration specifies the type and name of a variable. A variable definition is a declaration. In addition to specifying the name and type, a definition also allocates storage and may provide the variable with an initial value.
To obtain a declaration that is not also a definition, we add the extern
keyword and may not provide an explicit initializer:
extern int i; // declares but does not define i
int j; // declares and defines j
Any declaration that includes an explicit initializer is a definition. We can provide an initializer on a variable defined as extern
, but doing so overrides the extern
. An extern
that has an initializer is a definition:
extern double pi = 3.1416; // definition
It is an error to provide an initializer on an extern
inside a function.
The distinction between a declaration and a definition may seem obscure at this point but is actually important. To use a variable in more than one file requires declarations that are separate from the variable’s definition. To use the same variable in multiple files, we must define that variable in one—and only one—file. Other files that use that variable must declare—but not define—that variable.
We’ll have more to say about how C++ supports separate compilation in § 2.6.3 (p. 76) and § 6.1.3 (p. 207).
Exercise 2.11: Explain whether each of the following is a declaration or a definition:
(a) extern int ix = 1024;
(b) int iy;
(c) extern int iz;
C++ is a statically typed language, which means that types are checked at compile time. The process by which types are checked is referred to as type checking.
As we’ve seen, the type of an object constrains the operations that the object can perform. In C++, the compiler checks whether the operations we write are supported by the types we use. If we try to do things that the type does not support, the compiler generates an error message and does not produce an executable file.
As our programs get more complicated, we’ll see that static type checking can help find bugs. However, a consequence of static checking is that the type of every entity we use must be known to the compiler. As one example, we must declare the type of a variable before we can use that variable.