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.

  1. 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.
  2. 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).
  3. 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).
  4. Modify Menu.cpp to use switch statements instead of if statements.
  5. Write a program that evaluates the two expressions in the section labeled “precedence.”
  6. 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.
  7. 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.
  8. Write a program that uses all the trigraphs to see if your compiler supports them.
  9. Compile and run Static.cpp. Remove the static keyword from the code, compile and run it again, and explain what happens.
  10. Try to compile and link FileStatic.cpp with FileStatic2.cpp. What does the resulting error message mean?
  11. Modify Boolean.cpp so that it works with double values instead of ints.
  12. 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).
  13. 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.
  14. Modify Ifthen.cpp to use the ternary if-else operator (?:).
  15. 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.
  16. 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.
  17. 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.
  18. 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.
  19. Modify ArrayAddresses.cpp to work with the data types char, long int, float, and double.
  20. Apply the technique in ArrayAddresses.cpp to print out the size of the struct and the addresses of the array elements in StructArray.cpp.
  21. Create an array of string objects and assign a string to each element. Print out the array using a for loop.
  22. Create two new programs starting from ArgsToInts.cpp so they use atol( ) and atof( ), respectively.
  23. Modify PointerIncrement2.cpp so it uses a union instead of a struct.
  24. Modify PointerArithmetic.cpp to work with long and long double.
  25. 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).
  26. 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.
  27. 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.
  28. 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?
  29. (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).
  30. 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.
  31. 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.
  32. 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.
  33. 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.
  34. 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( ).
  35. 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.”

Home   |   Web Faq   |   Radio Online   |   About   |   Products   |   Webmaster Login

The quality software developer.™
© 2003-2004 ruben|labs corp. All Rights Reserved.
Timp de generare a paginii: 17583 secunde
Versiune site: 1.8 SP3 (build 2305-rtm.88542-10.2004)