Borland Online And The Cobb Group Present:


August, 1995 - Vol. 2 No. 8

Optimizing the IDE - Saving disk space by using common precompiled header files

If you're building complex Windows applications using the ObjectWindows Library (OWL), or if you're building large, multi-file DOS applications, you've probably used precompiled headers. Using precompiled headers saves compiler time because the compiler can simply store the binary image of a set of header-file symbols to a special precompiled header file (CSM) and then reload that file when it can, instead of having to recompile the same information.

Unfortunately, precompiled headers have a dark side: They occupy a great deal of disk space. For a simple DOS or Windows application, the CSM file probably won't be too large. However, on large projects with several header files and source modules­­the type of project that benefits from precompiled headers­­this file may take up 2 to 5 MB of disk space.

If you create and maintain only one or two applications, the disk space you're losing to CSM files may seem trivial. But if you're building and maintaining several applications, you'll create a separate CSM file for each project­­and the wasted disk space will quickly add up. (By default, the Integrated Development Environment, or IDE, creates a CSM for each new project in the project's main directory and then assigns to that file the same name as the current project.)

Instead, you can create one or two precompiled header files and share those files among your different projects. In fact, that's how Borland configured the example projects that ship with Borland C++ version 4.0. In this article, we'll show how you can create common CSM files for your projects.

Symbol sets

When you enable the Precompiled Headers option and begin building a project, the compiler gradually builds a set of symbols for the information that appears in the header files. Every time the compiler finishes processing the header files for a given source file, it stores the symbols for that source file in a distinct section of the CSM file. Along with the symbols, the compiler stores the following compiler environment settings:

This information, along with the symbols themselves, comprises a symbol set.

As the compiler scans subsequent source files, it first checks to see if the compiler environment settings match one of the symbol sets in the current CSM file. If the settings match, the compiler will reload the symbols from that symbol set in the current CSM file.

If the settings don't match, the compiler will repeat this process for any other symbol sets in the current CSM file. If the compiler reaches the end of the CSM file without finding a symbol set whose compiler environment matches, it will process the header files normally and then store the information in a new symbol set at the end of the CSM file, as shown in Figure A.


Figure A - The compiler stores source files' symbols in symbol sets within the project's CSM file.

Fortunately, there are some space-saving exceptions to this scenario. For example, if the list of header files stays the same but the compiler settings change, the compiler will replace the previous symbol set with the one that uses the new compiler settings. Similarly, if one source file uses a set of header files that's the same as the set another source file uses, except that it contains some additional header files, the compiler will merge the symbols from the additional files with the previous symbol set.

Shared CSM files

Since many of your projects will probably use the same or a very similar set of header files, you can force those projects to share a single CSM file. If multiple projects use the same compiler environment settings, you'll find that when you create new projects that use those settings, the compiler won't have to process the header files at all.

To force the compiler to use a specific CSM file for a given project, you can do one of three things: set the -H command-line option for BCC, add the #pragma hdrfile directive to the first source file in your project, or specify the file on the Precompiled Headers page of the Project Options dialog box.

For the OWL example projects that Borland includes with version 4.5 of the compiler, they've configured each project to use a precompiled header file named OWLWI.CSM in the BC45\LIB directory. If you've built any of the example projects, you can simply use this file as your common precompiled header file.

OWL projects

OWL projects take advantage of a powerful set of classes that simplify many aspects of Windows programming. However, these classes require a large number of header files.

Interestingly, most OWL projects will use the same set of OWL header files­­the ones that declare the application, window, and graphics classes. Rather than specifying the entire list of OWL header files each time you create an OWL project, you can instead use the OWLPCH.H file.

The OWLPCH.H file contains #include directives for all the standard OWL header files. Since the header files you create probably aren't as big as the OWL header files, you probably won't see a significant speed penalty if you tell the compiler to recompile only your header files and then reload the OWL header-file data from the CSM file. (We'll discuss this in more detail shortly.)

Non-OWL projects

For those projects that aren't OWL-based, you may want to use a precompiled header file that's separate from the one you use for OWL projects. By doing so, you'll be able to keep the symbols for these projects, which are generally smaller, out of the precompiled header file that contains all the OWL project symbols. Also, by keeping two common precompiled header files, you'll reduce the effects of an accidental Build All or Build Node command (see the section titled "Use Build All or Build Node with Caution," at the end of this article).

If you don't create a project file for a simple application, the IDE uses the BCWDEF.IDE default project file. This file specifies a precompiled header file named, not surprisingly, BCWDEF.CSM.

Each time you create an application without an explicit project, the IDE will use the BCWDEF.CSM file to store any precompiled header information. Since many non-OWL projects will probably have header file combinations similar to those of these "no project file" projects, you may want to use BCWDEF.CSM as your common precompiled header file for those projects as well.

CSM files remember everything

Unless you delete a project's CSM file, the compiler will simply add new symbol sets to the file as they appear in different source files. As we mentioned earlier, if a symbol set repeats in two source files, the compiler will simply read the symbols for the second source file from the CSM file.

Obviously, if you use a new sequence of header files or environment settings for each project, and they all use a common CSM file, that CSM file will keep growing until you stop creating projects or until you run out of disk space. This leads some programmers to believe that the precompiled headers option is wasteful and inefficient. However, if you use a consistent sequence of header files and environment options, it really isn't.

Stable environments

One way to ensure that the compiler doesn't encounter unique symbol sets is to make sure you don't add volatile header files to the CSM file. In other words, if you place only information from unchanging header files into the CSM file, the compiler will have a better opportunity to reuse that information. In contrast, if the CSM file contains header files with class declarations, and the class declarations change, the compiler will interpret those changed header files as a new symbol set, and it will append those symbols to the existing CSM file, causing it to grow.

As we mentioned earlier, your header files probably won't contain nearly as many symbols as the OWL library header files or other library header files do. If you force the compiler to process your header files each time you compile, you won't increase the build time significantly. The secret, then, is to be able to select the files the compiler will add to the CSM file. By design, the compiler will begin collecting symbols for the CSM file as it begins processing each source file.

However, you can tell the compiler to stop collecting this information by adding the #pragma hdrstop directive to your source files. To learn how you can tell the compiler when it should stop adding symbols to the CSM file, see Limiting the precompiled header file.

If you place the header file #include directives for a library (for example, OWLPCH.H) at the beginning of your source file, then insert the #pragma hdrstop directive, and finally add the #include directives for the application's specific header files, you'll keep your header file information out of the CSM file. In addition, you'll be processing the library header files (which probably take the most time to scan) as infrequently as possible. Now, let's create a simple example program that uses a common CSM file.

A simple example

To begin, launch the Borland C++ 4.x IDE. When the IDE's main window appears, choose New from the File menu, and enter the code from Listing A. When you finish entering this code, choose Save from the File menu, enter \pch_test.cpp in the File Name entry field of the Save File As dialog box, and click OK.


Listing A: PCH_TEST.CPP

#include <iostream.h>
#include <stdlib.h>

int main()
{
  cout << "This is only a test" << endl;
  return 0;
}

Next, right-click the editing window for PCH_TEST.H, and choose TargetExpert from the pop-up menu. In the TargetExpert dialog box, choose EasyWin from the Target Type list box and click OK.

Now, let's set the CSM filename for this default project. (Since you haven't explicitly created a project file, this example uses the default project file.) To do so, choose Project... from the Options menu, select the Precompiled Headers subtopic from the Compiler section of the Topics list box, and enter C:\COMMON.CSM in the Precompiled Header Name entry field, as shown in Figure B.

Figure B - You'll use the Project Options dialog box to set the precompiled header filename.

Since the IOSTREAM.H file directly and indirectly (by referencing other header files) pulls in more information than most of the other common header files, we'll tell the compiler to stop adding data to the CSM file after it processes that file. To do so, enter IOSTREAM.H in the Stop Precompiling After Header File entry field. Click OK to save these changes to the default project.

To build the PCH_TEST application, choose Build All from the Project menu. As the compiler builds the application, notice that it processes both of the header files.

When the build process is complete, click OK in the Compile Status dialog box, click on the editing window for the PCH_TEST.CPP file, and choose Close from the File menu. Choose New from the File menu to create a new editing window. In this window, enter the code from Listing A again. When you finish, choose Save from the File menu, enter \pch_tst2.cpp in the File Name entry field of the Save File As dialog box, and click OK.

Now, build this new application by choosing Make All from the Project menu. This time, you'll notice that the compiler doesn't process the IOSTREAM.H file, but it does process the STDLIB.H file as it did in the previous build.

(By the way, if you're using a Pentium or fast 80486 system, this small project will compile so quickly that it may be difficult to notice whether or not it processes the IOSTREAM.H file. If this is the case for your system, enter STDLIB.H in the Stop Precompiling After Header File entry field of the Project Options dialog box. This will force the compiler to reload all the header file information from the CSM file, which will make it easier for you to notice when it's not processing header files.)

Use a separate CSM file for final builds

If, when you build the release version of an application, you change the debug and diagnostic compiler settings (as you should), the compiler will rewrite the symbol set to reflect those new settings. To keep the compiler from doing so, change the CSM file setting to a new file in the project directory. Since the compiler setting changes would force the compiler to rebuild the CSM file anyway, you won't lose any time building a new one from scratch.

Use Build All or Build Node with caution

If you choose Build All from the Project menu or Build Node from the Project window's pop-up menu, the IDE will delete the current precompiled header file before rebuilding the project. If you're using a common precompiled header file and you don't mind rebuilding all the symbol images that exist in that file, you can use the Build All or Build Node command.

However, if you simply want to force a rebuild on each node, you can just use the TOUCH.EXE program to change the time/date stamp on the source files for the project. To do so, enter

touch *.cpp

in the main directory of the project. Then, you can use the Make All or Make Node commands to force the IDE to recompile and relink each of the project nodes.

Conclusion

Precompiled headers can save you a great deal of time by allowing the compiler to reload information that it's already processed. By sharing CSM files (the files that contain the precompiled symbols) among similar projects, you'll reduce the amount of space that CSM files occupy on your system, and you'll save time compiling the next project of that type.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.