C++builder是最快的C++编译器之一,从编译速度来说也可以说是最快的win32C++编译器了。除了速度之外,C++builder的性能也在其它C++编译器的之上,但许多delphi程序员仍受不了c++builder工程的编译速度。的确,delphi的速度要比任和c++的编译器都要快好多。Delphi在编译一个小工程的时候可能不到一秒,大的工程一般也在5秒钟这内编译完成了。
为什么delphi会比c++builder快这么多?是否有方法来c++builder的编译速度?本文就讲解了为什么C++的编译器速度会慢,并且介绍了一个简单的方法来减少c++builder的编译时间。
为什么c++编译器的速度会慢?
c++builder 使用者怎么通过预编译头文件来减少编译时间?
讲解基于VCL可视化工程的预编译头文件方法
优化c++builder对预编译头文件的使用
结论
注意事项
为什么c++编译器速度慢?
在C++中,你只能使用预定义或是预先声明了的函数,这意味什么?来看一个简单的例子,函数A()调用函数B(),函数A()只能在函数B()的原型或是函数体在A()之前才能调用它。下面的例子说明了这一点:
// declaration or prototype for B
void B();
void A()
{
B();
}
// definition, or function body of B
void B()
{
cout << "hello";
}
没有B()的原型,这个代码不会编译通过的,除非函数B()的函数体移到函数A()之前。
对于编译器来说,函数的原型很重要。当你运行程序时,编译器都要插入恰当的代码来调用程序。编译器必需知道要有多少个参数传给函数。也要知道函数的参数应该在栈里还是在寄存器里。总而言这,编译器必需知道怎么来产生正确的代码来调用这个函数,这就要求编译器必需知道预先声明或定义了的被调用的函数。
为使函数或类的原型简单化,C++提供了一个#include 指令。#include代表允许源文件在函数原型被调用的位置之前包含的一个头文件中找到函数原型。#include 指令在win32C++编程中很重要。C RTL函数的原型都包含在标准的头文件集中。win32API的原型全在微软提供的头文件集中,VCL中的类和函数的在原型则在随C++builder发行的头文件中。没有这些,你几乎做不了什么。
头文件提供了一种让程序员很容易管理的方式来执行C++的类型检查,但是也带来了很大的代价。当编译器运行到一个#include 指令时,它会打开这个头文件并插入到当前文件中,然后编译器象分析已编译的文件一样来分析这些包含进来的文件。当被包含的文件中还包含有其它的头文件时会怎么样呢?编译器仍会插入那个文件再分析它,想象一下,当10、20甚至100个文件被包含时呢?尽管如此数量的包含文件听起来很多,但当你加入window sdk头文件和所有vcl头文件时,这并不是不可能的。
来举个例子说明一下编译器是如何展开和翻译被包含的文件的。这是一个我用console wizard建立的一个简单的控制台程序。为了试验代码,在options-project-compiler在把pre-compiled headers选项关掉。
// include some standard header files
//包含了一些标准的头文件
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#pragma hdrstop
#include <condefs.h>
//-----------------------------------------------
int main()
{
printf("Hello from printf./n");
cout << "Hello from cout" << endl;
MessageBeep(0);
return 0;
}
当用c++builder编译工程时,编译进度对话框显示工程中包含有130,000行代码。13万行!怎么回事?源文件中只有大约四行代码,这13万行都包含在stdio.h,string.h,iostream.h,windows.h和被这四个头文件所包含的头文件里。
好,现在来看一下当工程中包含多个cpp文件时情况是怎么样的。停止编译这个工程,再加一个已有的文件到这个工程中。在第二个文件中加一个简单的函数。再到第一个文件中来调用这个新函数。
//-------------------------------------------------------
// UNIT1.CPP
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h" // prototype A() in unit1.h
#pragma hdrstop
void A()
{
printf("Hello from function A./n");
}
//-------------------------------------------------------
//-------------------------------------------------------
// PROJECT1.cpp
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h"
#pragma hdrstop
#include <condefs.h>
//-------------------------------------------------------
USEUNIT("Unit1.cpp");
//-------------------------------------------------------
int main()
{
printf("Hello from printf./n");
cout << "Hello from cout" << endl;
A();
MessageBeep(0);
return 0;
}
//-------------------------------------------------------
好,现在再来编译这个工程。如果在编译之前你关掉了pre-compiled头文件选项,当你编译完时你会发现编译进度对话框显示共有260,000行代码。可以看到,编译器不得不把两个文件都包含的相同的头文件集都做处理。在前面的例子里,编译器多处理了这些头文件带来的13万行代码。第二个文件又让编译器处理了同样的13万行代码,总共26万行。不难想象在一个大工程里行数将会以什么速度增长。一遍又一遍的处理这些相同的头文件大大的增加了编译的时间。
C++Builder是如何通过预编译头文件的方法来减少编译时间的
Borland的工程师认识到可以设计一个不用一遍又一遍处理同样的头文件的编译器来减少编译时间。于是Borland c++3.0中引入了预编译头文件的概念。处理方法很简单,当编译器处理源文件中的一组头文件时,把编译好的映象文件保存在磁盘上,当其它的源文件是引用同样的一组头文件时编译器直接读取编译好的文件而不是再一次分析。
修改一下刚才的控制台程序来看看预编译头文件是如何减少编译时间的。代码本身不用修改,仅仅把工程选项中的预编译头文件选项再选中就行了。选择Options-Project-Compiler并选中Use pre-compiled headers或是Cache pre-compiled headers。在预编译头文件名一栏中填入PCH.CSM。改好之后从重编译工程。
编译过程中注意一下编译进度对话框。你会发现当编译器编译project1.cpp时要处理130,000行代码,而编译UNIT1.cpp时只处理20行代码。当编译器分析第一个源文件时产生了一个预见编译头文件的映象,在处理第二个文件时直接用来提高编译速度。可以想象当源文件的数目不是2而是50时,性能会提高多少!
VCL GUI工程中预编译头文件的说明
通过预编译头文件的方法,上一个示例起码减少了50%的编译时间。而这不过仅仅是一个没什么功能的简单的控制台程序而已。你也会到在VCLGUI程序中会怎么样呢?缺省情况下,c++builder自动打开工程的预编译头文件选项的。但它仅仅对vcl.h文件进行预处理,这个头文件可以在任何一个窗体的源文件顶端找到。
#include <vcl.h>
#pragma hdrstop
#pragma hdrstop指令通知编译器停止产生预编译映象。在hdrstop指令之前的#include语句会被预编译,之后的就不会了。
那当vcl.h被预编译时到底有多少头文件也被预编译了呢?可以查看一下vcl.h,看到它包含了另一个叫做vcl0.h的头文件。如果你没有更改C++builder的缺省设置,vcl0.h就包含了一小组vcl的头文件,它们是:
// Core (minimal) VCL headers
//
#include <sysdefs.h>
#include <system.hpp>
#include <windows.hpp>
#include <messages.hpp>
#include <sysutils.hpp>
#include <classes.hpp>
#include <graphics.hpp>
#include <controls.hpp>
#include <forms.hpp>
#include <dialogs.hpp >
#include <stdctrls.hpp>
#include <extctrls.hpp>
这是一小部分常被重复包含的头文件,也许它只是大中型工程常用到的头文件的一个了集。vcl0.h还允许你通过定义条件(#define)来预编译更多的头文件。你可以通过#define一个叫做INC_VCLDB_HEADERS的变量来预编译数据库的头文件。同样,还可以定义INC_VCLEXT_HEADERS来预编译c++builder的扩展控件(Extra controls)。如果你还定义了INC_OLE_HEADERS,C++builder还会预编译一些SDK COM的头文件。这些定义要放在#include vcl.h语句这前。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop
注意:如果你要使用这些功能的话,你要确保把这两个#define加到每个cpp文件,即使是没有用到数据库或是扩展控件的文件。至于原因稍后会被讲到。
使用预编译头文件来优化c++builder。
缺省的预编译头文件设置确实减少了编译工程的时间。你可以通过打开和关闭观预编译头文件选项时完全编译一个大工程所要用的时间来证明这一点。本节的目的就是通过改善预编译头文件的方法来进一步减少编译时间。这里我要讲到两个技术。
在讨论这两个技术之前呢,有必要了解一下c++builder在编译源程序时是怎样来决定是否使用已存在的预编译的头文件的映象的。c++builder会给你工程中的每一个源文件都产生一个唯一的预编译头文件映象。这些映象被保存在硬盘上的一个文件里。编译器会在有两个源文件使用同样的预编译头文件映象时来重用一个映象文件。这是很重要的一点,两个源文件包含了完全一样的头文件时就会使用预编译头文件映象。再有就是他们包含这些头文件的顺序必需相同。简单的说:源文件的#pragma hdrstop指令之前必需完全相同。下面是一些例子:
示例1:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <iostream.h>
#pragma hdrstop #pragma hdrstop
示例2:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <stdio.h>
#include <iostream.h> #pragma hdrstop
#pragma hdrstop
示例3:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #pragma hdrstop
#pragma hdrstop #include <stdio.h>
示例4:预编译映象匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <iostream.h> #include <iostream.h>
#include <windows.h> #include <windows.h>
#include "unit1.h" #include "unit1.h"
#pragma hdrstop #pragma hdrstop
示例5:预编译映象匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#define INC_VCLDB_HEADERS #define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS #define INC_VCLEXT_HEADERS
#include <vcl.h> #include <vcl.h>
#pragma hdrstop #pragma hdrstop
#include "unit1.h" #include "unit2.h"
示例6:预编译映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#define INC_VCLDB_HEADERS #include <vcl.h>
#define INC_VCLEXT_HEADERS #pragma hdrstop
#include <vcl.h>
#pragma hdrstop
当编译器处理一个预编译映象不匹配的源文件时就会重新再产生一个全新的映象。看上面的示例2。尽管stdio.h在unit1.cpp中已经被编译过了,但是unit2中还是要被编译的。只有在多个文件中都能用到预编译映象编译器才能减少编译的时间。
这就是我所讲到的技术的基础。预编译尽可能多的头文件,并确保在每个源文件中都用到同样的预编译映象。
技术1:
第一个技术只是通过在每个源文件中定义两个条件来增加vcl.h中包含的头文件的数目。打开工程中的每个cpp文件包括工程文件,象下面看到的那样改变它们的头两行。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop
如果你不喜欢这种方法,你可以在Project-Options-Directories/Conditional条件定义栏中填入INC_VCLDB_HEADERS 和 INC+VCLEXT_HEADERS来达到同样的目的。
或许你也想插入一些windows.h之类的你常用的C RTL头文件,要确保插入到hdrstop pragma之前,而且顺序要相同。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#include <windows.h>
#include <stdio.h>
#pragma hdrstop
技术2:
技术1的效果很好,但它不是很灵活。如果你想要在观感编译的头文件列表中再加入一个新的头文件的话,你要修改你工程中的每一个源文件。此外,技术一容易出错。如果你把包含的顺序弄乱了,你只会更糟而不是更好。
技术二处理了技术一的一些缺点。方法就是创建一个巨大的头文件来包含你工程中用到的所有的头文件。这个头文件将包含有VCL的头文件、window SDK的头文件以及RTL头文件。当然你也可以包含一些创建的窗体的头文件,但稍会你会了解到不应该把一些常修改的文件做预编译处理(查看注意事项:不要预编译变化的头文件)
下面就样一个文件的示例:
//---------------------------------------------------------
// PCH.H: Common header file
#ifndef PCH_H
#define PCH_H
// include every VCL header that we use
// could include vcl.h instead
#include <Buttons.hpp>
#include <Classes.hpp>
#include <ComCtrls.hpp>
#include <Controls.hpp>
#include <ExtCtrls.hpp>
#include <Forms.hpp>
#include <Graphics.hpp>
#include <ToolWin.hpp>
// include the C RTL headers that we use
#include <string.h>
#include <iostream.h>
#include <stdio.h>
// include headers for the 3rd party controls
// TurboPower System
#include "StBase.hpp"
#include "StVInfo.hpp"
// Our custom controls
#include "DBDatePicker.h"
#include "DBRuleCombo.h"
#include "DBPhonePanel.h"
// Object Repository header files
#include "BaseData.h"
#include "BASEDLG.h"
// project include files
// pre-compile these only if PRECOMPILE_ALL is defined
#ifdef PRECOMPILE_ALL
#include "About.hpp"
#include "mainform.h"
...
... // about 60 more files
...
#include "validate.h"
#endif
#endif
一旦你有巨大的共同的头文件准备就绪,改变每一个源文件,以便它包含此文件。我选择保留原来的include 声明vcl.h不变。你可能想要移动vcl.h 到共同的头文件。
//-----------------------------------------------
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
注意:在您为每一个C + +源文件添加include pch.h以后,不要再在# pragma hdrstop前插入include files 。这样做将导致这些C + +文件需要的预编译映像与其他文件预编译映像不匹配。
结果: 我目前使用技术二,没有预定义precompile_all就我目前的项目。该项目是中型客户机/服务器数据库程序,由113个 C + +源文件构成的,其中大部分是forms 或datamodules 。使用技术2 ,full build项目,只需要195秒。该195秒, 40秒用生成预编译的头文件,大约40秒,是花了链接。在余下的时间,编译器编译113个 C + +源文件。这是平均的每秒一个文件。相比之下,如果没有预编译头使用,该项目需时30分钟以上building,如果预编译头使用,但技术二是没有使用,该项目需时18分钟。
增量make在没有头文件改变时使用技术2是闪电一样的快。如果没有头文件已经改变,编译器并不理会在磁盘上再生预编译的映像。当这个条件是满足,一个增量make只需要1或2秒,因为只有C + +源文件有变更的需要加以编译。编译器花费所有的时间编制这些文件,而不是浪费时间编译系统头文件。当一个头文件改变,增量make的速度取决于是否precompile_all标志被界定。
注释: 不要预编译常数变量:如果头文件还包含一个常数变量并且赋值则编译器不能预先编译头文件。例如,把下面一行放在一个头文件可以阻止建立一个预编译的头文件映像:
const AnsiString strError = "An Error Occurred!!!!!";
如果你想要常量的变量放在一个头文件,创建一个单独的头文件包含常量和不预编译该文件。尽量不让这头文件包括其他文件减少编译器负担。同样地,不包括此文件从其他的头文件如果你能。如果这好像一项艰巨的任务,您可以使用extern 关键字。创建一个头文件包含外部的原型为您的常数。然后创建一个cpp文件中定义的常量(即让他们有一个值) 。
注意 const variables 只发生在当您这样做:界定precompile_all标志。当您不界定这个标帜,您自己的头文件是不预编译的。如果您不预编译头文件,那么您就可以添加常数,它没有任何问题。此外,#define's 并不代表一个问题,只是刚才常量的变量(虽然我不建议您切换回##define's)
不要预编译模板的headers:这个建议是基于实证。我有一个模板类的几个inline functions。整个模板类居住在一个头文件。编译器能够预编译此头文件,但我注意到,该预编译的映像在增量make时总是重新产生。我认为这与模板被编译器处理方式有关。我可以预先编译STL headers没有任何问题,所以我不知道什么问题。我建议你要大胆向前并且尝试编译模板headers,但密切注意编译进度对话。您可能需要停止预编译模板头文件如果他们会造成很多问题。
密切留意编译进度对话:编译进度对话告诉您的预编译头工作进行的如何。当您使用技术2 ,你应该看到,编译器需要很长时间,编译第一个C + +源文件在您的项目。在编译第一个文件时编译器生成预编译头的映像在该项目。在这段时间内,您应该看到行数在编译器的进展对话达到数量庞大( 100000-500000 ) 。一旦编译器移动到其他的C + +档案,行数应可能介乎二十至1000行,每个源文件,如果你定义precompile_all标志。如果你不定义这个标志,行数应留下的15000或如此。一旦编译器完成编译的第一个文件,在该项目,随后的文件只应花1到2秒编译。
如果编译器在一个C + +档案停顿超过4秒,你可能有一个源文件的预编译映像和创造共同的头文件的映像不匹配。行数是另一个指标。如果您看到行数执行了50000行以上在一个源文件,这是一个很好的迹象表明编译器无法适用现行预编译的映像到该源文件。
不预编译改变的头文件:当使用技术2 ,认识到,任何一个小的改变到一个头文件,将迫使编译器再生预编译的映像。根据试验结果,这可能需要从20秒到1分钟。如果您的头文件经常变动,您可能会想要预编译系统唯一的VCL和头文件。这是precompile_all标志的宗旨。它可以让您轻易地包括或删除您的头文件,从预编译的映像。
//---------------------------------------------------------
// PCH.H: Common header file
#ifndef PCH_H
#define PCH_H
// include every VCL header that we use
#include <Buttons.hpp>
// include the C RTL headers that we use
#include <string.h>
#include <iostream.h>
#include <stdio.h>
// project include files
// pre-compile these only if PRECOMPILE_ALL is defined
#ifdef PRECOMPILE_ALL
#include "About.h"
#include "mainform.h"
...
... // about 60 more files
...
#include "validate.h"
#endif
#endif
为了预编译您自己的头文件,添加precompile_all到conditional defines在Project - Options - Directories/Conditional。如果您的头文件经常变动,那么不添加此有条件的界定。当您不预编译您自己的头文件,全面建立您的项目将需要花上一点时间更长的时间。然而,当您作出改变在您的其中一个头文件,增量make将更快,因为编译器不会浪费20-60秒重建预编译的映像。
我记下了当PRECOMPILE_ALL 定义时full build需要花费的时间,然后我做了一个小小的更改在我的主窗口的头文件中,并且我做了一个增量make,接下来,我重复了这个过程并没有定义PRECOMPILE_ALL ,这里是结果。
Not defined (do not pre-compile my headers)
-------------------------------------------------------------
Full Build: 195 sec 408887 lines compiled
Inc Make : 28 sec 7 files affected: 27059 lines compiled
Defined (pre-compile my headers)
-------------------------------------------------------------
Full Build: 179 sec 255689 lines
Inc Make : 179 sec all files affected: 255689 lines
注意全面构建是比我预编译我自己的头文件要快16秒,但看看发生什么事,当我做一个增量make在改变一个头文件之后。增量make花费的时间和一个全面的构建一样 。当您预编译自己的头文件,编译器重建预编译的映像,在您每次改变一个头文件。此外,当预编译的映像发生变化,每个依赖于该映像的文件将会重新编译。因此,如果你改变头文件,整个项目基本上得到重建。当我这样做并没有预先编译我自己的头文件,预编译的映像没有得到重建。这样可以使增量make时间减少到 28秒相比179秒。
往往你可能执行的增量make比全面构建多10次以上,这似乎是明智的不断增量make时间下来,即使这意味着要全面构建将慢10 %。这是我的办法。我不界定precompile_all的值在我的任何项目。
不移除现有的#include statements:创建一个共同的头文件并不意味着你应该删除include statements从您的头文件和C + +源文件。保留这些include statements在他们所在的位置。原因有好几个,您应该保留现有的include statements。首先,如果您删除include statements从您的头文件,在C + + Builder将简单的添加他们回来一次。其次,你可能希望停止预编译某些文件,这将迫使你添加include statements回到自己的源代码文件。最后,留下include statements不变,您可以保留必要的包含秩序之间的头文件。如果您删除include statements,您将需要担心你的列表顺序include statements在您的共同header。
include files防止多重包含,因此您不必担心,包括相同的文件两次。下面的代码是从结果一节采取,从该项目的mainform。它显示在源文件中include statements 仍然存在,即使常用header已经包括了他们。
//----------------------------------------------------------------------
// MAINFORM.CPP
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
#include <system.hpp>
#include "mainform.h"
#include "About.h"
#include "util.h"
#include "claim.h"
#include "expert.h"
#include "vendor.h"
#include "lawfirm.h"
#include "registry.h"
#include "exceptions.h"
...
...
//----------------------------------------------------------------------
// MAINFORM.H
#ifndef mainformH
#define mainformH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Menus.hpp>
#include <ComCtrls.hpp>
#include <ToolWin.hpp>
#include <ExtCtrls.hpp>
#include <Buttons.hpp>
#include "defs.h"
//----------------------------------------------------------------------
class TMDIParent : public TForm
{
__published:
...
这点显示此代码是为了证明,即使我使用一个共同文件,包括所谓的pch.h ,我也不移除现有的include statements 从我的源文件。当我创建一个新的源文件,我补充include statements ,使文件编译不依靠includes 从common header。一旦新的源文件编译,我插入一个包括声明,为common header,以保持编译次数下降。
观察include statements大小写敏感性:有人在C + + Builder中的IDE组发布了评论,编译器观察到的情况匹配时,预编译的映像。如果您包括common header不同的情况下使用在不同单元,编译器会生成预编译的形象为每一个。下面的代码示例显示了你不应该这样做的部分。
//----------------------------------------------------------------------
// MAINFORM.CPP
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
...
//----------------------------------------------------------------------
// ABOUT.CPP
#include <vcl.h> // OK. same case as mainform.cpp
#include "pch.h"
#pragma hdrstop
...
//----------------------------------------------------------------------
// SPLASH.CPP
#include <vcl.h>
#include "PCH.H" // WRONG. mismatched case
#pragma hdrstop
...
//----------------------------------------------------------------------
// LOGON.CPP
#include <Vcl.h> // WRONG. mismatched case
#include "pch.h"
#pragma hdrstop
...
在这个例子中,编译器会生成并使用相同的预编译的映像为mainform.cpp和about.cpp ,但splash.cpp和logon.cpp将分别产生自己的预编译映像,这将拖慢编译时间。该法则是这样的:# pragma hdrstop指令之前列出的每一个包含文件应同其他文件使用相同的方法。# pragma hdrstop指令下面的 Include statements,没有相匹配的情况,因为他们不预编译。
考虑common header加入vcl.h :技术2的例子代码中common header不包括档案vcl.h.每一个CPP 源文件,既包括vcl.h和pch.h ,这样的。
//-----------------------------------------------
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
您可能会喜欢vcl.h包含在common header中,如果你这样做,然后每个cpp文件可以简单,common header。
//-----------------------------------------------
#include "pch.h"
#pragma hdrstop
您可以告诉您的项目使用其自己的预编译头文件通过为预编译映像指定一个文件名,这个选项是对项目选项卡上Compiler 选项对话框。更改此值由$(BCB)/lib/vcl50.csm ,与其他项目不会冲突,如pch.csm 。
为每一个项目用一个单独的CSM的档案有几个优势。首先,它可以让您为每个项目创建一个自定义您的共同的头文件。其次,由于CSM的文件中的lib目录是共享的,它的大小往往是成长的。这并不罕见,有一个共同的CSM的文件是大于30 MB的。最后,有些用户报告说,共用相同的CSM的横跨多个项目是编译器魅影错误一个来源,。如果您收到奇怪的编译器的错误algorithm.h ,你也许能解决问题通过不分享您的CSM的文件横跨多个项目。
# 00文件的数目在您的项目目录:当C + + Builder中产生一个预编译的header映像,它以副档名为. CSM的扩展名保存它。它还将保存一个或多个文件用扩展名为 #?? (即#00 ,#01 ,#02 ) 。。 #??档案的数目取决于 你如何优化您的预编译头文件
如果您优化他们的完美, C + + Builder中会生成一个单独的文件. # 00 。如果您不优化您的文件正确,您会看到其他类似的文件与档案的扩展名 ( 。 # 01 , 。 # 02等) 。在场的额外#00的文件表明,你有没有优化您的header正确。
因此,这些是什么文件?对于. CSM的文件中每个独特的预编译的映像, C + + Builder中产生的一档名为. #??扩建工程,从# 00 开始。 。这些文件通常介于1和2 MB的大小。当您有优化您的档案完美, 。 CSM的文件会包含且仅有一个预编译的映像。因此,您最终拥有一个。 CSM的文件和一档名为。 # 00的文件。当您不优化您的项目, 。 CSM的文件可能包含许多独特的预编译的映像。每一个独特的映像,产生一个额外的。 #??文件。
Here are a couple of additional tips regarding these mysterious .#00 files. C++Builder generates these files as its needs them, but it never deletes them. Because these files are fairly large, you should delete them every so often. Also, because C++Builder never deletes them, you may see .#00 files even after you optimize your pre-compiled headers. Third, these files are created in the same directory that the CSM file resides. By default, this is the $(BCB)/lib directory. Lastly, you may want to take a look in your $(BCB)/lib and count how many of these files are lying around. In the previous note, we talked about how C++Builder generates one common .CSM file in the lib directory that all projects use. This shared .CSM file tends to be big, and it also tends to contain many different pre-compiled images. Since each unique image generates a separate .#00 file, you end up with tons of .#00 files in your lib directory. Currently, my BCB4 lib directory contains five of these files ( vcl40.#00,vcl40.#01, vcl40.#02, vcl40.#03, and vcl40.#04).
为什么delphi会比c++builder快这么多?是否有方法来c++builder的编译速度?本文就讲解了为什么C++的编译器速度会慢,并且介绍了一个简单的方法来减少c++builder的编译时间。
为什么c++编译器的速度会慢?
c++builder 使用者怎么通过预编译头文件来减少编译时间?
讲解基于VCL可视化工程的预编译头文件方法
优化c++builder对预编译头文件的使用
结论
注意事项
为什么c++编译器速度慢?
在C++中,你只能使用预定义或是预先声明了的函数,这意味什么?来看一个简单的例子,函数A()调用函数B(),函数A()只能在函数B()的原型或是函数体在A()之前才能调用它。下面的例子说明了这一点:
// declaration or prototype for B
void B();
void A()
{
B();
}
// definition, or function body of B
void B()
{
cout << "hello";
}
没有B()的原型,这个代码不会编译通过的,除非函数B()的函数体移到函数A()之前。
对于编译器来说,函数的原型很重要。当你运行程序时,编译器都要插入恰当的代码来调用程序。编译器必需知道要有多少个参数传给函数。也要知道函数的参数应该在栈里还是在寄存器里。总而言这,编译器必需知道怎么来产生正确的代码来调用这个函数,这就要求编译器必需知道预先声明或定义了的被调用的函数。
为使函数或类的原型简单化,C++提供了一个#include 指令。#include代表允许源文件在函数原型被调用的位置之前包含的一个头文件中找到函数原型。#include 指令在win32C++编程中很重要。C RTL函数的原型都包含在标准的头文件集中。win32API的原型全在微软提供的头文件集中,VCL中的类和函数的在原型则在随C++builder发行的头文件中。没有这些,你几乎做不了什么。
头文件提供了一种让程序员很容易管理的方式来执行C++的类型检查,但是也带来了很大的代价。当编译器运行到一个#include 指令时,它会打开这个头文件并插入到当前文件中,然后编译器象分析已编译的文件一样来分析这些包含进来的文件。当被包含的文件中还包含有其它的头文件时会怎么样呢?编译器仍会插入那个文件再分析它,想象一下,当10、20甚至100个文件被包含时呢?尽管如此数量的包含文件听起来很多,但当你加入window sdk头文件和所有vcl头文件时,这并不是不可能的。
来举个例子说明一下编译器是如何展开和翻译被包含的文件的。这是一个我用console wizard建立的一个简单的控制台程序。为了试验代码,在options-project-compiler在把pre-compiled headers选项关掉。
// include some standard header files
//包含了一些标准的头文件
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#pragma hdrstop
#include <condefs.h>
//-----------------------------------------------
int main()
{
printf("Hello from printf./n");
cout << "Hello from cout" << endl;
MessageBeep(0);
return 0;
}
当用c++builder编译工程时,编译进度对话框显示工程中包含有130,000行代码。13万行!怎么回事?源文件中只有大约四行代码,这13万行都包含在stdio.h,string.h,iostream.h,windows.h和被这四个头文件所包含的头文件里。
好,现在来看一下当工程中包含多个cpp文件时情况是怎么样的。停止编译这个工程,再加一个已有的文件到这个工程中。在第二个文件中加一个简单的函数。再到第一个文件中来调用这个新函数。
//-------------------------------------------------------
// UNIT1.CPP
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h" // prototype A() in unit1.h
#pragma hdrstop
void A()
{
printf("Hello from function A./n");
}
//-------------------------------------------------------
//-------------------------------------------------------
// PROJECT1.cpp
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <windows.h>
#include "Unit1.h"
#pragma hdrstop
#include <condefs.h>
//-------------------------------------------------------
USEUNIT("Unit1.cpp");
//-------------------------------------------------------
int main()
{
printf("Hello from printf./n");
cout << "Hello from cout" << endl;
A();
MessageBeep(0);
return 0;
}
//-------------------------------------------------------
好,现在再来编译这个工程。如果在编译之前你关掉了pre-compiled头文件选项,当你编译完时你会发现编译进度对话框显示共有260,000行代码。可以看到,编译器不得不把两个文件都包含的相同的头文件集都做处理。在前面的例子里,编译器多处理了这些头文件带来的13万行代码。第二个文件又让编译器处理了同样的13万行代码,总共26万行。不难想象在一个大工程里行数将会以什么速度增长。一遍又一遍的处理这些相同的头文件大大的增加了编译的时间。
C++Builder是如何通过预编译头文件的方法来减少编译时间的
Borland的工程师认识到可以设计一个不用一遍又一遍处理同样的头文件的编译器来减少编译时间。于是Borland c++3.0中引入了预编译头文件的概念。处理方法很简单,当编译器处理源文件中的一组头文件时,把编译好的映象文件保存在磁盘上,当其它的源文件是引用同样的一组头文件时编译器直接读取编译好的文件而不是再一次分析。
修改一下刚才的控制台程序来看看预编译头文件是如何减少编译时间的。代码本身不用修改,仅仅把工程选项中的预编译头文件选项再选中就行了。选择Options-Project-Compiler并选中Use pre-compiled headers或是Cache pre-compiled headers。在预编译头文件名一栏中填入PCH.CSM。改好之后从重编译工程。
编译过程中注意一下编译进度对话框。你会发现当编译器编译project1.cpp时要处理130,000行代码,而编译UNIT1.cpp时只处理20行代码。当编译器分析第一个源文件时产生了一个预见编译头文件的映象,在处理第二个文件时直接用来提高编译速度。可以想象当源文件的数目不是2而是50时,性能会提高多少!
VCL GUI工程中预编译头文件的说明
通过预编译头文件的方法,上一个示例起码减少了50%的编译时间。而这不过仅仅是一个没什么功能的简单的控制台程序而已。你也会到在VCLGUI程序中会怎么样呢?缺省情况下,c++builder自动打开工程的预编译头文件选项的。但它仅仅对vcl.h文件进行预处理,这个头文件可以在任何一个窗体的源文件顶端找到。
#include <vcl.h>
#pragma hdrstop
#pragma hdrstop指令通知编译器停止产生预编译映象。在hdrstop指令之前的#include语句会被预编译,之后的就不会了。
那当vcl.h被预编译时到底有多少头文件也被预编译了呢?可以查看一下vcl.h,看到它包含了另一个叫做vcl0.h的头文件。如果你没有更改C++builder的缺省设置,vcl0.h就包含了一小组vcl的头文件,它们是:
// Core (minimal) VCL headers
//
#include <sysdefs.h>
#include <system.hpp>
#include <windows.hpp>
#include <messages.hpp>
#include <sysutils.hpp>
#include <classes.hpp>
#include <graphics.hpp>
#include <controls.hpp>
#include <forms.hpp>
#include <dialogs.hpp >
#include <stdctrls.hpp>
#include <extctrls.hpp>
这是一小部分常被重复包含的头文件,也许它只是大中型工程常用到的头文件的一个了集。vcl0.h还允许你通过定义条件(#define)来预编译更多的头文件。你可以通过#define一个叫做INC_VCLDB_HEADERS的变量来预编译数据库的头文件。同样,还可以定义INC_VCLEXT_HEADERS来预编译c++builder的扩展控件(Extra controls)。如果你还定义了INC_OLE_HEADERS,C++builder还会预编译一些SDK COM的头文件。这些定义要放在#include vcl.h语句这前。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop
注意:如果你要使用这些功能的话,你要确保把这两个#define加到每个cpp文件,即使是没有用到数据库或是扩展控件的文件。至于原因稍后会被讲到。
使用预编译头文件来优化c++builder。
缺省的预编译头文件设置确实减少了编译工程的时间。你可以通过打开和关闭观预编译头文件选项时完全编译一个大工程所要用的时间来证明这一点。本节的目的就是通过改善预编译头文件的方法来进一步减少编译时间。这里我要讲到两个技术。
在讨论这两个技术之前呢,有必要了解一下c++builder在编译源程序时是怎样来决定是否使用已存在的预编译的头文件的映象的。c++builder会给你工程中的每一个源文件都产生一个唯一的预编译头文件映象。这些映象被保存在硬盘上的一个文件里。编译器会在有两个源文件使用同样的预编译头文件映象时来重用一个映象文件。这是很重要的一点,两个源文件包含了完全一样的头文件时就会使用预编译头文件映象。再有就是他们包含这些头文件的顺序必需相同。简单的说:源文件的#pragma hdrstop指令之前必需完全相同。下面是一些例子:
示例1:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <iostream.h>
#pragma hdrstop #pragma hdrstop
示例2:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <stdio.h>
#include <iostream.h> #pragma hdrstop
#pragma hdrstop
示例3:预编译头文件映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #pragma hdrstop
#pragma hdrstop #include <stdio.h>
示例4:预编译映象匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <iostream.h> #include <iostream.h>
#include <windows.h> #include <windows.h>
#include "unit1.h" #include "unit1.h"
#pragma hdrstop #pragma hdrstop
示例5:预编译映象匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#define INC_VCLDB_HEADERS #define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS #define INC_VCLEXT_HEADERS
#include <vcl.h> #include <vcl.h>
#pragma hdrstop #pragma hdrstop
#include "unit1.h" #include "unit2.h"
示例6:预编译映象不匹配
//-------------------- //--------------------
// UNIT1.CPP // UNIT2.CPP
#define INC_VCLDB_HEADERS #include <vcl.h>
#define INC_VCLEXT_HEADERS #pragma hdrstop
#include <vcl.h>
#pragma hdrstop
当编译器处理一个预编译映象不匹配的源文件时就会重新再产生一个全新的映象。看上面的示例2。尽管stdio.h在unit1.cpp中已经被编译过了,但是unit2中还是要被编译的。只有在多个文件中都能用到预编译映象编译器才能减少编译的时间。
这就是我所讲到的技术的基础。预编译尽可能多的头文件,并确保在每个源文件中都用到同样的预编译映象。
技术1:
第一个技术只是通过在每个源文件中定义两个条件来增加vcl.h中包含的头文件的数目。打开工程中的每个cpp文件包括工程文件,象下面看到的那样改变它们的头两行。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#pragma hdrstop
如果你不喜欢这种方法,你可以在Project-Options-Directories/Conditional条件定义栏中填入INC_VCLDB_HEADERS 和 INC+VCLEXT_HEADERS来达到同样的目的。
或许你也想插入一些windows.h之类的你常用的C RTL头文件,要确保插入到hdrstop pragma之前,而且顺序要相同。
#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#include <vcl.h>
#include <windows.h>
#include <stdio.h>
#pragma hdrstop
技术2:
技术1的效果很好,但它不是很灵活。如果你想要在观感编译的头文件列表中再加入一个新的头文件的话,你要修改你工程中的每一个源文件。此外,技术一容易出错。如果你把包含的顺序弄乱了,你只会更糟而不是更好。
技术二处理了技术一的一些缺点。方法就是创建一个巨大的头文件来包含你工程中用到的所有的头文件。这个头文件将包含有VCL的头文件、window SDK的头文件以及RTL头文件。当然你也可以包含一些创建的窗体的头文件,但稍会你会了解到不应该把一些常修改的文件做预编译处理(查看注意事项:不要预编译变化的头文件)
下面就样一个文件的示例:
//---------------------------------------------------------
// PCH.H: Common header file
#ifndef PCH_H
#define PCH_H
// include every VCL header that we use
// could include vcl.h instead
#include <Buttons.hpp>
#include <Classes.hpp>
#include <ComCtrls.hpp>
#include <Controls.hpp>
#include <ExtCtrls.hpp>
#include <Forms.hpp>
#include <Graphics.hpp>
#include <ToolWin.hpp>
// include the C RTL headers that we use
#include <string.h>
#include <iostream.h>
#include <stdio.h>
// include headers for the 3rd party controls
// TurboPower System
#include "StBase.hpp"
#include "StVInfo.hpp"
// Our custom controls
#include "DBDatePicker.h"
#include "DBRuleCombo.h"
#include "DBPhonePanel.h"
// Object Repository header files
#include "BaseData.h"
#include "BASEDLG.h"
// project include files
// pre-compile these only if PRECOMPILE_ALL is defined
#ifdef PRECOMPILE_ALL
#include "About.hpp"
#include "mainform.h"
...
... // about 60 more files
...
#include "validate.h"
#endif
#endif
一旦你有巨大的共同的头文件准备就绪,改变每一个源文件,以便它包含此文件。我选择保留原来的include 声明vcl.h不变。你可能想要移动vcl.h 到共同的头文件。
//-----------------------------------------------
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
注意:在您为每一个C + +源文件添加include pch.h以后,不要再在# pragma hdrstop前插入include files 。这样做将导致这些C + +文件需要的预编译映像与其他文件预编译映像不匹配。
结果: 我目前使用技术二,没有预定义precompile_all就我目前的项目。该项目是中型客户机/服务器数据库程序,由113个 C + +源文件构成的,其中大部分是forms 或datamodules 。使用技术2 ,full build项目,只需要195秒。该195秒, 40秒用生成预编译的头文件,大约40秒,是花了链接。在余下的时间,编译器编译113个 C + +源文件。这是平均的每秒一个文件。相比之下,如果没有预编译头使用,该项目需时30分钟以上building,如果预编译头使用,但技术二是没有使用,该项目需时18分钟。
增量make在没有头文件改变时使用技术2是闪电一样的快。如果没有头文件已经改变,编译器并不理会在磁盘上再生预编译的映像。当这个条件是满足,一个增量make只需要1或2秒,因为只有C + +源文件有变更的需要加以编译。编译器花费所有的时间编制这些文件,而不是浪费时间编译系统头文件。当一个头文件改变,增量make的速度取决于是否precompile_all标志被界定。
注释: 不要预编译常数变量:如果头文件还包含一个常数变量并且赋值则编译器不能预先编译头文件。例如,把下面一行放在一个头文件可以阻止建立一个预编译的头文件映像:
const AnsiString strError = "An Error Occurred!!!!!";
如果你想要常量的变量放在一个头文件,创建一个单独的头文件包含常量和不预编译该文件。尽量不让这头文件包括其他文件减少编译器负担。同样地,不包括此文件从其他的头文件如果你能。如果这好像一项艰巨的任务,您可以使用extern 关键字。创建一个头文件包含外部的原型为您的常数。然后创建一个cpp文件中定义的常量(即让他们有一个值) 。
注意 const variables 只发生在当您这样做:界定precompile_all标志。当您不界定这个标帜,您自己的头文件是不预编译的。如果您不预编译头文件,那么您就可以添加常数,它没有任何问题。此外,#define's 并不代表一个问题,只是刚才常量的变量(虽然我不建议您切换回##define's)
不要预编译模板的headers:这个建议是基于实证。我有一个模板类的几个inline functions。整个模板类居住在一个头文件。编译器能够预编译此头文件,但我注意到,该预编译的映像在增量make时总是重新产生。我认为这与模板被编译器处理方式有关。我可以预先编译STL headers没有任何问题,所以我不知道什么问题。我建议你要大胆向前并且尝试编译模板headers,但密切注意编译进度对话。您可能需要停止预编译模板头文件如果他们会造成很多问题。
密切留意编译进度对话:编译进度对话告诉您的预编译头工作进行的如何。当您使用技术2 ,你应该看到,编译器需要很长时间,编译第一个C + +源文件在您的项目。在编译第一个文件时编译器生成预编译头的映像在该项目。在这段时间内,您应该看到行数在编译器的进展对话达到数量庞大( 100000-500000 ) 。一旦编译器移动到其他的C + +档案,行数应可能介乎二十至1000行,每个源文件,如果你定义precompile_all标志。如果你不定义这个标志,行数应留下的15000或如此。一旦编译器完成编译的第一个文件,在该项目,随后的文件只应花1到2秒编译。
如果编译器在一个C + +档案停顿超过4秒,你可能有一个源文件的预编译映像和创造共同的头文件的映像不匹配。行数是另一个指标。如果您看到行数执行了50000行以上在一个源文件,这是一个很好的迹象表明编译器无法适用现行预编译的映像到该源文件。
不预编译改变的头文件:当使用技术2 ,认识到,任何一个小的改变到一个头文件,将迫使编译器再生预编译的映像。根据试验结果,这可能需要从20秒到1分钟。如果您的头文件经常变动,您可能会想要预编译系统唯一的VCL和头文件。这是precompile_all标志的宗旨。它可以让您轻易地包括或删除您的头文件,从预编译的映像。
//---------------------------------------------------------
// PCH.H: Common header file
#ifndef PCH_H
#define PCH_H
// include every VCL header that we use
#include <Buttons.hpp>
// include the C RTL headers that we use
#include <string.h>
#include <iostream.h>
#include <stdio.h>
// project include files
// pre-compile these only if PRECOMPILE_ALL is defined
#ifdef PRECOMPILE_ALL
#include "About.h"
#include "mainform.h"
...
... // about 60 more files
...
#include "validate.h"
#endif
#endif
为了预编译您自己的头文件,添加precompile_all到conditional defines在Project - Options - Directories/Conditional。如果您的头文件经常变动,那么不添加此有条件的界定。当您不预编译您自己的头文件,全面建立您的项目将需要花上一点时间更长的时间。然而,当您作出改变在您的其中一个头文件,增量make将更快,因为编译器不会浪费20-60秒重建预编译的映像。
我记下了当PRECOMPILE_ALL 定义时full build需要花费的时间,然后我做了一个小小的更改在我的主窗口的头文件中,并且我做了一个增量make,接下来,我重复了这个过程并没有定义PRECOMPILE_ALL ,这里是结果。
Not defined (do not pre-compile my headers)
-------------------------------------------------------------
Full Build: 195 sec 408887 lines compiled
Inc Make : 28 sec 7 files affected: 27059 lines compiled
Defined (pre-compile my headers)
-------------------------------------------------------------
Full Build: 179 sec 255689 lines
Inc Make : 179 sec all files affected: 255689 lines
注意全面构建是比我预编译我自己的头文件要快16秒,但看看发生什么事,当我做一个增量make在改变一个头文件之后。增量make花费的时间和一个全面的构建一样 。当您预编译自己的头文件,编译器重建预编译的映像,在您每次改变一个头文件。此外,当预编译的映像发生变化,每个依赖于该映像的文件将会重新编译。因此,如果你改变头文件,整个项目基本上得到重建。当我这样做并没有预先编译我自己的头文件,预编译的映像没有得到重建。这样可以使增量make时间减少到 28秒相比179秒。
往往你可能执行的增量make比全面构建多10次以上,这似乎是明智的不断增量make时间下来,即使这意味着要全面构建将慢10 %。这是我的办法。我不界定precompile_all的值在我的任何项目。
不移除现有的#include statements:创建一个共同的头文件并不意味着你应该删除include statements从您的头文件和C + +源文件。保留这些include statements在他们所在的位置。原因有好几个,您应该保留现有的include statements。首先,如果您删除include statements从您的头文件,在C + + Builder将简单的添加他们回来一次。其次,你可能希望停止预编译某些文件,这将迫使你添加include statements回到自己的源代码文件。最后,留下include statements不变,您可以保留必要的包含秩序之间的头文件。如果您删除include statements,您将需要担心你的列表顺序include statements在您的共同header。
include files防止多重包含,因此您不必担心,包括相同的文件两次。下面的代码是从结果一节采取,从该项目的mainform。它显示在源文件中include statements 仍然存在,即使常用header已经包括了他们。
//----------------------------------------------------------------------
// MAINFORM.CPP
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
#include <system.hpp>
#include "mainform.h"
#include "About.h"
#include "util.h"
#include "claim.h"
#include "expert.h"
#include "vendor.h"
#include "lawfirm.h"
#include "registry.h"
#include "exceptions.h"
...
...
//----------------------------------------------------------------------
// MAINFORM.H
#ifndef mainformH
#define mainformH
//----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Menus.hpp>
#include <ComCtrls.hpp>
#include <ToolWin.hpp>
#include <ExtCtrls.hpp>
#include <Buttons.hpp>
#include "defs.h"
//----------------------------------------------------------------------
class TMDIParent : public TForm
{
__published:
...
这点显示此代码是为了证明,即使我使用一个共同文件,包括所谓的pch.h ,我也不移除现有的include statements 从我的源文件。当我创建一个新的源文件,我补充include statements ,使文件编译不依靠includes 从common header。一旦新的源文件编译,我插入一个包括声明,为common header,以保持编译次数下降。
观察include statements大小写敏感性:有人在C + + Builder中的IDE组发布了评论,编译器观察到的情况匹配时,预编译的映像。如果您包括common header不同的情况下使用在不同单元,编译器会生成预编译的形象为每一个。下面的代码示例显示了你不应该这样做的部分。
//----------------------------------------------------------------------
// MAINFORM.CPP
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
...
//----------------------------------------------------------------------
// ABOUT.CPP
#include <vcl.h> // OK. same case as mainform.cpp
#include "pch.h"
#pragma hdrstop
...
//----------------------------------------------------------------------
// SPLASH.CPP
#include <vcl.h>
#include "PCH.H" // WRONG. mismatched case
#pragma hdrstop
...
//----------------------------------------------------------------------
// LOGON.CPP
#include <Vcl.h> // WRONG. mismatched case
#include "pch.h"
#pragma hdrstop
...
在这个例子中,编译器会生成并使用相同的预编译的映像为mainform.cpp和about.cpp ,但splash.cpp和logon.cpp将分别产生自己的预编译映像,这将拖慢编译时间。该法则是这样的:# pragma hdrstop指令之前列出的每一个包含文件应同其他文件使用相同的方法。# pragma hdrstop指令下面的 Include statements,没有相匹配的情况,因为他们不预编译。
考虑common header加入vcl.h :技术2的例子代码中common header不包括档案vcl.h.每一个CPP 源文件,既包括vcl.h和pch.h ,这样的。
//-----------------------------------------------
#include <vcl.h>
#include "pch.h"
#pragma hdrstop
您可能会喜欢vcl.h包含在common header中,如果你这样做,然后每个cpp文件可以简单,common header。
//-----------------------------------------------
#include "pch.h"
#pragma hdrstop
您可以告诉您的项目使用其自己的预编译头文件通过为预编译映像指定一个文件名,这个选项是对项目选项卡上Compiler 选项对话框。更改此值由$(BCB)/lib/vcl50.csm ,与其他项目不会冲突,如pch.csm 。
为每一个项目用一个单独的CSM的档案有几个优势。首先,它可以让您为每个项目创建一个自定义您的共同的头文件。其次,由于CSM的文件中的lib目录是共享的,它的大小往往是成长的。这并不罕见,有一个共同的CSM的文件是大于30 MB的。最后,有些用户报告说,共用相同的CSM的横跨多个项目是编译器魅影错误一个来源,。如果您收到奇怪的编译器的错误algorithm.h ,你也许能解决问题通过不分享您的CSM的文件横跨多个项目。
# 00文件的数目在您的项目目录:当C + + Builder中产生一个预编译的header映像,它以副档名为. CSM的扩展名保存它。它还将保存一个或多个文件用扩展名为 #?? (即#00 ,#01 ,#02 ) 。。 #??档案的数目取决于 你如何优化您的预编译头文件
如果您优化他们的完美, C + + Builder中会生成一个单独的文件. # 00 。如果您不优化您的文件正确,您会看到其他类似的文件与档案的扩展名 ( 。 # 01 , 。 # 02等) 。在场的额外#00的文件表明,你有没有优化您的header正确。
因此,这些是什么文件?对于. CSM的文件中每个独特的预编译的映像, C + + Builder中产生的一档名为. #??扩建工程,从# 00 开始。 。这些文件通常介于1和2 MB的大小。当您有优化您的档案完美, 。 CSM的文件会包含且仅有一个预编译的映像。因此,您最终拥有一个。 CSM的文件和一档名为。 # 00的文件。当您不优化您的项目, 。 CSM的文件可能包含许多独特的预编译的映像。每一个独特的映像,产生一个额外的。 #??文件。
Here are a couple of additional tips regarding these mysterious .#00 files. C++Builder generates these files as its needs them, but it never deletes them. Because these files are fairly large, you should delete them every so often. Also, because C++Builder never deletes them, you may see .#00 files even after you optimize your pre-compiled headers. Third, these files are created in the same directory that the CSM file resides. By default, this is the $(BCB)/lib directory. Lastly, you may want to take a look in your $(BCB)/lib and count how many of these files are lying around. In the previous note, we talked about how C++Builder generates one common .CSM file in the lib directory that all projects use. This shared .CSM file tends to be big, and it also tends to contain many different pre-compiled images. Since each unique image generates a separate .#00 file, you end up with tons of .#00 files in your lib directory. Currently, my BCB4 lib directory contains five of these files ( vcl40.#00,vcl40.#01, vcl40.#02, vcl40.#03, and vcl40.#04).