|














Am avut 371730 vizite de la lansarea siteului.

|
|
Inapoi
Inainte
Cuprins
Make: managing separate compilation
When using separate compilation
(breaking code into a number of
translation units), you need some way to automatically compile each file and to
tell the linker to build all the pieces – along with the appropriate
libraries and startup code – into an executable file. Most compilers allow
you to do this with a single command-line statement. For the GNU C++ compiler,
for example, you might say
g++ SourceFile1.cpp SourceFile2.cpp
The problem with this approach is that
the compiler will first compile each individual file, regardless of whether that
file needs to be rebuilt or not. With many files in a project, it can
become prohibitive to recompile everything if you’ve changed only a single
file.
The solution to this problem, developed
on Unix but available everywhere in some form, is a program called make.
The make utility manages all the individual files in a project by
following the instructions in a text file called a
makefile. When you edit some of the files in a
project and type make, the make program follows the guidelines in
the makefile to compare the dates on the source code files to the dates
on the corresponding target files, and if a source code file date is more recent
than its target file, make invokes the compiler on the source code file.
make only recompiles the source code files that were changed, and any
other source-code files that are affected by the modified files. By using
make, you don’t have to re-compile all the files in your project
every time you make a change, nor do you have to check to see that everything
was built properly. The makefile contains all the commands to put your
project together. Learning to use make will save you a lot of time and
frustration. You’ll also discover that make is the typical way that
you install new software on a Linux/Unix machine (although those
makefiles tend to be far more complicated than the ones presented in this
book, and you’ll often automatically generate a makefile for your
particular machine as part of the installation process).
Because make is available in some
form for virtually all C++ compilers (and even if it isn’t, you can use
freely-available makes with any compiler), it will be the tool used
throughout this book. However, compiler vendors have also created their own
project building tools. These tools ask you which files
are in your project and determine all the relationships themselves. These tools
use something similar to a makefile, generally called a project
file, but the programming environment maintains this file so you don’t
have to worry about it. The configuration and use of project files varies from
one development environment to another, so you must find the appropriate
documentation on how to use them (although project file tools provided by
compiler vendors are usually so simple to use that you can learn them by playing
around – my favorite form of education).
The makefiles used within this
book should work even if you are also using a specific vendor’s
project-building
tool.
Make activities
When you type make (or whatever
the name of your “make” program happens to be), the make
program looks in the current directory for a file named makefile, which
you’ve created if it’s your project. This file lists
dependencies between source code
files. make looks at the dates on files. If a dependent file has an older
date than a file it depends on, make executes the
rule given after the dependency.
All comments in
makefiles start with a # and continue to the end of the
line.
As a simple example, the makefile
for a program called “hello” might contain:
# A comment
hello.exe: hello.cpp
mycompiler hello.cpp
This says that hello.exe (the
target) depends on hello.cpp. When hello.cpp has a newer date than
hello.exe, make executes the “rule” mycompiler
hello.cpp. There may be multiple dependencies and multiple rules. Many
make programs require that all the rules begin with a tab. Other than
that, whitespace is generally ignored so you can format for
readability.
The rules are not restricted to being
calls to the compiler; you can call any program you want from within
make. By creating groups of interdependent dependency-rule sets, you can
modify your source code files, type make and be certain that all the
affected files will be rebuilt correctly.
Macros
A makefile may contain
macros (note that these are completely different
from C/C++ preprocessor macros). Macros allow convenient
string replacement. The makefiles in this book use a macro to invoke the
C++ compiler. For example,
CPP = mycompiler
hello.exe: hello.cpp
$(CPP) hello.cpp
The = is used to identify CPP
as a macro, and the $ and parentheses expand the macro. In this case,
the expansion means that the macro call $(CPP) will be replaced with the
string mycompiler. With the macro above, if you want to change to a
different compiler called cpp, you just change the macro
to:
CPP = cpp
You can also add compiler flags, etc., to
the macro, or use separate macros to add compiler flags.
Suffix Rules
It becomes tedious to tell make
how to invoke the compiler for every single cpp file in your project,
when you know it’s the same basic process each time. Since make is
designed to be a time-saver, it also has a way to abbreviate actions, as long as
they depend on file name suffixes. These abbreviations are called
suffix rules. A suffix rule
is the way to teach make how to convert a file with one type of extension
(.cpp, for example) into a file with another type of extension
(.obj or .exe). Once you teach make the rules for producing
one kind of file from another, all you have to do is tell make which
files depend on which other files. When make finds a file with a date
earlier than the file it depends on, it uses the rule to create a new file.
The suffix rule tells make that it
doesn’t need explicit rules to build everything, but instead it can figure
out how to build things based on their file extension. In this case it says
“To build a file that ends in exe from one that ends in cpp,
invoke the following command.” Here’s what it looks like for the
example above:
CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
$(CPP) $<
The
.SUFFIXES directive tells
make that it should watch out for any of the following file-name
extensions because they have special meaning for this particular makefile. Next
you see the suffix rule .cpp.exe, which says “Here’s how to
convert any file with an extension of cpp to one with an extension of
exe” (when the cpp file is more recent than the exe
file). As before, the $(CPP) macro is used, but then you see something
new: $<. Because this begins with a
‘$’ it’s a macro, but this is one of
make’s special built-in macros. The $< can be used only
in suffix rules, and it means “whatever prerequisite triggered the
rule” (sometimes called the dependent), which in this case
translates to “the cpp file that needs to be
compiled.”
Once the suffix rules have been set up,
you can simply say, for example, “make Union.exe,” and the
suffix rule will kick in, even though there’s no mention of
“Union” anywhere in the makefile.
Default targets
After the macros and suffix rules,
make looks for the first “target” in a file, and builds that,
unless you specify differently. So for the following
makefile:
CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
$(CPP) $<
target1.exe:
target2.exe:
If you just type
‘make’, then target1.exe will be built (using the
default suffix rule) because that’s the first target that make
encounters. To build target2.exe you’d have to explicitly say
‘make target2.exe’. This becomes tedious, so you normally
create a default “dummy” target that depends on all the rest of the
targets, like this:
CPP = mycompiler
.SUFFIXES: .exe .cpp
.cpp.exe:
$(CPP) $<
all: target1.exe target2.exe
Here, ‘all’ does not
exist and there’s no file called ‘all’, so every time
you type make, the program sees ‘all’ as the first
target in the list (and thus the default target), then it sees that
‘all’ does not exist so it had better make it by checking all
the dependencies. So it looks at target1.exe and (using the suffix rule)
sees whether (1) target1.exe exists and (2) whether target1.cpp is
more recent than target1.exe, and if so runs the suffix rule (if you
provide an explicit rule for a particular target, that rule is used instead).
Then it moves on to the next file in the default target list. Thus, by creating
a default target list (typically called ‘all’ by convention,
but you can call it anything) you can cause every executable in your project to
be made simply by typing ‘make’. In addition, you can have
other non-default target lists that do other things – for example, you
could set it up so that typing ‘make debug’ rebuilds all your
files with debugging wired
in.
Makefiles in this book
Using the program ExtractCode.cpp
from Volume 2 of this book, all the code listings in this book are automatically
extracted from the ASCII text version of this book and placed in subdirectories
according to their chapters. In addition, ExtractCode.cpp creates several
makefiles in each subdirectory (with different names) so you can simply
move into that subdirectory and type make -f mycompiler.makefile
(substituting the name of your compiler for ‘mycompiler’, the
‘-f’ flag says “use what follows as the
makefile”). Finally, ExtractCode.cpp creates a
“master” makefile in the root directory where the
book’s files have been expanded, and this makefile descends into
each subdirectory and calls make with the appropriate makefile.
This way you can compile all the code in the book by invoking a single
make command, and the process will stop whenever your compiler is unable
to handle a particular file (note that a Standard C++ conforming compiler should
be able to compile all the files in this book). Because implementations of
make vary from system to system, only the most basic, common features are
used in the generated makefiles.
An example makefile
As mentioned, the code-extraction tool
ExtractCode.cpp automatically generates makefiles for each
chapter. Because of this, the makefiles for each chapter will not be
placed in the book (all the makefiles are packaged with the source code, which
you can download from www.BruceEckel.com). However, it’s
useful to see an example of a makefile. What follows is a shortened
version of the one that was automatically generated for this chapter by the
book’s extraction tool. You’ll find more than one makefile in
each subdirectory (they have different names; you invoke a specific one with
‘make -f’). This one is for GNU C++:
CPP = g++
OFLAG = -o
.SUFFIXES : .o .cpp .c
.cpp.o :
$(CPP) $(CPPFLAGS) -c $<
.c.o :
$(CPP) $(CPPFLAGS) -c $<
all: \
Return \
Declare \
Ifthen \
Guess \
Guess2
# Rest of the files for this chapter not shown
Return: Return.o
$(CPP) $(OFLAG)Return Return.o
Declare: Declare.o
$(CPP) $(OFLAG)Declare Declare.o
Ifthen: Ifthen.o
$(CPP) $(OFLAG)Ifthen Ifthen.o
Guess: Guess.o
$(CPP) $(OFLAG)Guess Guess.o
Guess2: Guess2.o
$(CPP) $(OFLAG)Guess2 Guess2.o
Return.o: Return.cpp
Declare.o: Declare.cpp
Ifthen.o: Ifthen.cpp
Guess.o: Guess.cpp
Guess2.o: Guess2.cpp
The macro CPP is set to the name of the
compiler. To use a different compiler, you can either edit the makefile
or change the value of the macro on the command line, like
this:
make CPP=cpp
Note, however, that
ExtractCode.cpp has an automatic scheme to automatically build
makefiles for additional compilers.
The second macro OFLAG is the flag
that’s used to indicate the name of the output file. Although many
compilers automatically assume the output file has the same base name as the
input file, others don’t (such as Linux/Unix compilers, which default to
creating a file called a.out).
You can see that there are two suffix
rules here, one for cpp files and one for .c files (in case any C
source code needs to be compiled). The default target is all, and each
line for this target is “continued” by using the backslash, up until
Guess2, which is the last one in the list and thus has no backslash.
There are many more files in this chapter, but only these are shown here for the
sake of brevity.
The suffix rules take care of creating
object files (with a .o extension) from cpp files, but in general
you need to explicitly state rules for creating the executable, because normally
an executable is created by linking many different object files and make
cannot guess what those are. Also, in this case (Linux/Unix) there is no
standard extension for executables so a suffix rule won’t work for these
simple situations. Thus, you see all the rules for building the final
executables explicitly stated.
This makefile takes the absolute
safest route of using as few make features as possible; it only uses the
basic make concepts of targets and dependencies, as well as macros. This
way it is virtually assured of working with as many make programs as
possible. It tends to produce a larger makefile, but that’s not so
bad since it’s automatically generated by
ExtractCode.cpp.
There are lots of other make
features that this book will not use, as well as newer and cleverer versions and
variations of make with advanced shortcuts that can save a lot of time.
Your local documentation may describe the further features of your particular
make, and you can learn more about make from Managing Projects
with Make by Oram and Talbott (O’Reilly, 1993). Also, if your compiler
vendor does not supply a make or it uses a non-standard make, you
can find GNU make for virtually any platform in existence by searching the
Internet for GNU archives (of which there are
many).
Summary
This chapter was a fairly intense tour
through all the fundamental features of C++ syntax, most of which are inherited
from and in common with C (and result in C++’s vaunted backwards
compatibility with C). Although some C++ features were introduced here, this
tour is primarily intended for people who are conversant in programming, and
simply need to be given an introduction to the syntax basics of C and C++. If
you’re already a C programmer, you may have even seen one or two things
about C here that were unfamiliar, aside from the C++ features that were most
likely new to you. However, if this chapter has still seemed a bit overwhelming,
you should go through the CD ROM course Thinking in C: Foundations for C++
and Java (which contains lectures, exercises, and guided solutions), which
is bound into this book, and also available at
www.BruceEckel.com.
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
- Create a header file (with
an extension of ‘.h’). In this file, declare a group of
functions by varying the argument lists and return values from among the
following: void, char, int, and float. Now create a
.cpp file that includes your header file and creates definitions for all
of these functions. Each definition should simply print out the function name,
argument list, and return type so you know it’s been called. Create a
second .cpp file that includes your header file and defines int
main( ), containing calls to all of your functions. Compile and run
your program.
- Write
a program that uses two nested for loops and the modulus operator
(%) to detect and print prime numbers (integral numbers that are not
evenly divisible by any other numbers except for themselves and
1).
- Write a program
that uses a while loop to read words from standard input (cin)
into a string. This is an “infinite” while loop, which
you break out of (and exit the program) using a break statement. For each
word that is read, evaluate it by first using a sequence of if
statements to “map” an integral value to the word, and then use a
switch statement that uses that integral value as its selector (this
sequence of events is not meant to be good programming style; it’s just
supposed to give you exercise with control flow). Inside each case,
print something meaningful. You must decide what the
“interesting” words are and what the meaning is. You must also
decide what word will signal the end of the program. Test the program by
redirecting a file into the program’s standard input (if you want to save
typing, this file can be your program’s source
file).
- Modify
Menu.cpp to use switch statements instead of if
statements.
- Write a
program that evaluates the two expressions in the section labeled
“precedence.”
- Modify
YourPets2.cpp so that it uses various different data types (char,
int, float, double, and their variants). Run the program
and create a map of the resulting memory layout. If you have access to more than
one kind of machine, operating system, or compiler, try this experiment with as
many variations as you can
manage.
- Create two
functions, one that takes a string* and one that takes a
string&. Each of these functions should modify the outside string
object in its own unique way. In main( ), create and initialize
a string object, print it, then pass it to each of the two functions,
printing the
results.
- Write a
program that uses all the trigraphs to see if your compiler supports
them.
- Compile and
run Static.cpp. Remove the static keyword from the code, compile
and run it again, and explain what
happens.
- Try to
compile and link FileStatic.cpp with FileStatic2.cpp. What does
the resulting error message
mean?
- Modify
Boolean.cpp so that it works with double values instead of
ints.
- Modify
Boolean.cpp and Bitwise.cpp so they use the explicit operators (if
your compiler is conformant to the C++ Standard it will support
these).
- Modify
Bitwise.cpp to use the functions from Rotation.cpp. Make sure you
display the results in such a way that it’s clear what’s happening
during
rotations.
- Modify
Ifthen.cpp to use the ternary if-else operator
(?:).
- Create
a struct that holds two string objects and one int. Use a
typedef for the struct name. Create an instance of the
struct, initialize all three values in your instance, and print
them out. Take the address of your instance and assign it to a pointer to your
struct type. Change the three values in your instance and print them out,
all using the
pointer.
- Create a
program that uses an enumeration of colors. Create a variable of this
enum type and print out all the numbers that correspond with the color
names, using a for
loop.
- Experiment
with Union.cpp by removing various union elements to see the
effects on the size of the resulting union. Try assigning to one element
(thus one type) of the union and printing out a via a different element
(thus a different type) to see what
happens.
- Create a
program that defines two int arrays, one right after the other. Index off
the end of the first array into the second, and make an assignment. Print out
the second array to see the changes cause by this. Now try defining a
char variable between the first array definition and the second, and
repeat the experiment. You may want to create an array printing function to
simplify your
coding.
- Modify
ArrayAddresses.cpp to work with the data types char, long
int, float, and
double.
- Apply
the technique in ArrayAddresses.cpp to print out the size of the
struct and the addresses of the array elements in
StructArray.cpp.
- Create
an array of string objects and assign a string to each element. Print out
the array using a for
loop.
- Create two new
programs starting from ArgsToInts.cpp so they use atol( ) and
atof( ),
respectively.
- Modify
PointerIncrement2.cpp so it uses a union instead of a
struct.
- Modify
PointerArithmetic.cpp to work with long and long
double.
- Define a
float variable. Take its address, cast that address to an unsigned
char, and assign it to an unsigned char pointer. Using this pointer
and [ ], index into the float variable and use the
printBinary( ) function defined in this chapter to print out a map
of the float (go from 0 to sizeof(float)). Change the value of the
float and see if you can figure out what’s going on (the
float contains encoded
data).
- Define an
array of int. Take the starting address of that array and use
static_cast to convert it into an void*. Write a function that
takes a void*, a number (indicating a number of bytes), and a value
(indicating the value to which each byte should be set) as arguments. The
function should set each byte in the specified range to the specified value. Try
out the function on your array of
int.
- Create a
const array of double and a volatile array of
double. Index through each array and use const_cast to cast each
element to non-const and non-volatile, respectively, and assign a
value to each
element.
- Create a
function that takes a pointer to an array of double and a value
indicating the size of that array. The function should print each element in the
array. Now create an array of double and initialize each element to zero,
then use your function to print the array. Next use reinterpret_cast to
cast the starting address of your array to an unsigned char*, and set
each byte of the array to 1 (hint: you’ll need to use sizeof to
calculate the number of bytes in a double). Now use your array-printing
function to print the results. Why do you think each element was not set to the
value
1.0?
- (Challenging)
Modify FloatingAsBinary.cpp so that it prints out each part of the
double as a separate group of bits. You’ll have to replace the
calls to printBinary( ) with your own specialized code (which you
can derive from printBinary( )) in order to do this, and
you’ll also have to look up and understand the floating-point format along
with the byte ordering for your compiler (this is the challenging
part).
- Create a
makefile that not only compiles YourPets1.cpp and YourPets2.cpp
(for your particular compiler) but also executes both programs as part of the
default target behavior. Make sure you use suffix
rules.
- Modify
StringizingExpressions.cpp so that P(A) is conditionally
#ifdefed to allow the debugging code to be automatically stripped out by
setting a command-line flag. You will need to consult your compiler’s
documentation to see how to define and undefine preprocessor values on the
compiler command
line.
- Define a
function that takes a double argument and returns an int. Create
and initialize a pointer to this function, and call the function through your
pointer.
- Declare a
pointer to a function taking an int argument and returning a pointer to a
function that takes a char argument and returns a
float.
- Modify
FunctionTable.cpp so that each function returns a string (instead
of printing out a message) and so that this value is printed inside of
main( ).
- Create
a makefile for one of the previous exercises (of your choice) that allows
you to type make for a production build of the program, and make
debug for a build of the program including debugging
information.
[30]
Note that all conventions seem to end after the agreement that some sort of
indentation take place. The feud between styles of code formatting is unending.
See Appendix A for the description of this book’s coding
style.
[31]
Thanks to Kris C. Matson for suggesting this exercise topic.
[32]
Unless you take the very strict approach that “all argument passing in
C/C++ is by value, and the ‘value’ of an array is what is produced
by the array identifier: it’s address.” This can be seen as true
from the assembly-language standpoint, but I don’t think it helps when
trying to work with higher-level concepts. The addition of references in C++
makes the “all passing is by value” argument more confusing, to the
point where I feel it’s more helpful to think in terms of “passing
by value” vs. “passing addresses.”
 |
| |
|