文档库 最新最全的文档下载
当前位置:文档库 › DELPHI的编译指令

DELPHI的编译指令

DELPHI的编译指令
DELPHI的编译指令

DELPHI的编译指令

{$IFDEF WIN32} -- 这可不是批注喔!

对于Delphi来说﹐左右大括号之间的内容是批注﹐然而「{$」(左括号后紧接着货币符号)对于Compiler(编译器)而言并不是批注﹐而是写给Compiler看的特别指示。

应用时机与场合

Delphi中有许许多多的Compiler Directives(编译器指令)﹐这些编译指令对于我们的程序发展有何影响呢? 它们又能帮我们什么忙呢?

Compiler Directive 对程序开发的影响与助益, 可以从以下几个方向来讨论:

协助除错

版本分类

程序的重用与管理

设定统一的执行环境

协助除错

稳健熟练的程序设计师经常会在开发应用系统的过程中﹐特别加入一些除错程序或者回馈验算的程序﹐这些除错程序对于软件品质的提升有极其正面的功能。然而开发完成的正式版本中如果不需要这些额外的程序的话﹐要想在一堆程序中找出哪些是除错用的程序并加以删除或设定为批注﹐不仅累人﹐而且容易出错﹐况且日后维护时这些除错程序还用得着。此时如果能够应用像是$IFDEF的Compiler Directives ﹐就可以轻易的指示Delphi要/不要将某一段程序编进执行文件中。

同时﹐Compiler本身也提供了一些错误检查的开关﹐可以预先对程序中可能的问题提醒

程序设计师注意﹐同样有助于撰写正确的程序。

版本分类

除了上述的除错版本/正式版本的分类之外﹐对于像是「试用版」「普及版」「专业版」的版本分类﹐也可以经由Compiler Directive的使用﹐为最后的产品设定不同的使用权限。其它诸如「中文版」「日文版」「国际标准版」等全球版本管理方面﹐同样也可以视需要指示Delphi特别连结哪些资源档或者是采用哪些适当的程序。以上的两则例子中﹐各版本间只需共享同一份程序代码即可。

Delphi 1.0 与Delphi 2.0有许多不同之处﹐组件资源文件(.DCR)即是其中一例﹐两者的档案格式并不兼容﹐在您读过本文之后﹐相信可以写出这样的程序﹐指示Delphi在不同的版本采用适当的资源文件以利于组件的安装。

{$IFDEF WIN32}

{$R XXX32.DCR}

{$R XXXX16.DCR}

{$EDNIF}

程序的重用与管理

经过前文的讨论后﹐相信你已经不难看出Compiler Directives在程序管理上的应用价值。对于原始程序的重用与管理﹐也是Compiler Directives 使得上力的地方. 举例来说:

Pascal-Style字符串是Delphi 1.0与Delphi 2.0之间的明显差异﹐除了原先的短字符串之外﹐Delphi 2.0之后还多了更为方便使用的长字符串﹐同时﹐系统也额外提供了像是Trim() 这样的字符串处理函式。假如您有一个字符串处理单元必须要同时应用于Delphi 1.0 与2.0的项目时﹐编译指示器可以帮你的忙。

此外﹐透过像是{$I xxxx} 这样的Compiler Directives﹐我们也可以适当的含入某些

程序, 同样有助于切割组合我们的程序或编译设定。

设定一致的执行环境

项目小组的成员间﹐必须有共同的环境设定﹐我很难预料一个小组成员间彼此有不同的{$B}{$H}{$X}设定﹐最后子系统在并入主程序时会发生什么事。

此外, 当您写好一个组件或单元需要交予第三者使用时, 使用编译指示器也可以保证组件

使用者与您有相同的编译环境。

使用Compiler Directives

指令语法

Compiler Directives从外表看起来与批注颇为类似, 与批注不同的是:Compiler Directives的语法格式都是以「{$」开始, 不空格紧接一个名称(或一个字母)表明给Compiler的特别指示, 再加上其它的开关或参数内容, 最后以右大括号作为指令的结束, 例如:

{$B+}

{$R-}

{$R MyCursor.res}

同时, 就如同Pascal的变量名称与保留字一样, Compiler Directives也是不区分大小写的。

从指令的语法格式来说Compiler Directives﹐可以进一步分类成以下三种格式:

开关指令(Switch directives)

这类指令都是单一字母以不空格的方式连接「+」或「-」符号; 或者是开关名称以一个空格后连接「ON」或「OFF」来表示作用/关闭某一个编译指示开关。例如:

{$ALIGN ON}

开关型的编译指令不一定要分行写, 它们可以组合在同一个编译指示的批注符号之间, 但

必须以逗号连接, 而且中间不可以有空格, 例如:

{$B+,H+,T-,J+}

光标停留在程序编辑器的任一位置时按下Ctrl+O O, 完整的Compiler Directives将会全部列于Unit的最上方。

参数指令(Parameter directives)

有些Compiler Directives需要在编译名称后面连接自定的参数(文件名称或指定的内存大小), 例如: {$R MyCursor.res}, 即在指示Delphi在编译连结时, 含入「MyCursor.res」这个资源档。

条件指令(Conditional directives)

指示Compiler在编译的过程中, 按我们设定的条件, 选择性的采用/排除不同区域的程序代码。

以下是一个条件编译的例子, 第一与第三列是写给Compiler看的,指示Compiler在

__DEBUG这个条件名称完成定义的情况才编译ShowMessage()这列程序;反之, 如果

__DEBUG 当时没有定义的话, 这段程序几乎与批注无异, Compiler对它将视而不见。{$IFDEF __DEBUG}

ShowMessage(IntToStr(i));

{$ENDIF}

如何从IDE改变Compiler directives设定

从Delphi的IDE程序整合发展环境, 我们很方便的就可以修改各个compiler directives 的设定, 方法是: 从Delphi IDE主选单: Project/Options/Compiler, 直接核选/取消各个CheckBox。值得注意的是, 改变一个项目的Compiler directives并不会影响其它的项目, 换言之, 各个项目都保有自己一套编译指示。

假如您希望其它的项目也采用相同一套的Compiler directives, 在上述ProjectOptions 对话盒的左下方有一个「Default」选项, 选取这个CheckBox之后, 虽然对于既有的项目没有作用, 但未来新的项目都将可以采用这组设定作为默认值。

将Compiler directives写入程序

透过Delphi的整合环境设定Compiler directives的确十分简便, 但是许多情况下我们仍然需要将Compiler directive直接加到程序中。至少有两个原因支持我们这么作:

局部控制编译条件

在Project/Options/Compiler中所作的设定, 影响所及是整个项目, 如果某一段程序要特别使用不同的编译设定, 就必须直接将编译指示加到程序中。

下列这段取自Online Help的程序范例, 即应用了{$I}编译指令局部控制在发生I/O错误时不要举发例外讯息, 这样, 我们就可以编译出一支在这段程序区域中不会产生I/O例外

讯息的档案侦测函数。

function FileExists(FileName: string): Boolean;

var

F: file;

begin

{$I-}

AssignFile(F, FileName);

FileMode := 0; ( Set file access to read only }

Reset(F);

CloseFile(F);

{$I+}

FileExists := (IOResult = 0) and (FileName <> '''');

end; { FileExists }

程序的可移植性

我们都可能会用到其它公司或个人创作的unit或component, 也可能分享程序给其它人, 换句话说, 单元或程序可能会在不同的机器上编译, 直接将Compiler directives加入程序, 不仅可以免去程序使用前需要特别更改IDE的麻烦, 更重要的是解决了各个单元间要求不同编译环境的歧异。

注意事项

Compiler directives的作用与影响范围

如同变量的可见范围与生命周期, 在我们使用Compiler Directives 时也必须注意各个Compiler Directives 的作用范围.

Compiler Directives的作用范围可分为以下两种:

全域的

全域的Compiler Directives, 影响所及是整个项目; 我们稍早前提到经由Delphi IDE改变Compiler directives的方式就属于全域的设定。

区域的

而区域的Compiler Directives 影响所及只从Compiler Directives 改变的那一行开始, 直到该程序单元(Unit)的结束或另一个相同的Compiler Directives 为止, 对其它的程序单元并没有影响。

也就是说, 如果在unit中特别加入Compiler directives, Compiler会优先采用区域的设定, 然后才是属于项目层级的全域设定。

值得一提的是, 在程序中直接加入Compiler directives的最大作用范围也只限于当时那个单元而已, 对其它单元并没有任何影响, 即使是以uses参考也是一样。

也就是说, 我们可以透过uses参考其它unit公开的变量与函式, 但是各个unit的编译指令并不会互相参考。

这项独立的性质, 使得unit之间编译环境的设定与关系变得十分简洁, 例如Delphi 2.0的VCL都是在{$H+}的情况下编译的, 因此, VCL中的字符串都是以长字符串的型态编译而成的, 有了这项编译指令独立的特性, 不论我们Prject中的设定为何, 这些在VCL中定义过的字符串都是长字符串。我们的Project也不会因为uses了VCL中的unit而改变了自己的设定。

因此, 在我们移交程序到网络上时, 大可以放心的在程序中加入必要的Compiler directives, 别担心, 即使别的unit以uses参考了我们的程序, 也不影响它自己原来的设定。

如果我们自行以{$DEFINE _DEBUGVERSION}($DEFINE在稍后的个别指令介绍中将有说明)定义了一个条件符号, 这个新的条件符号也是区域的, 换句话说, 它只从定义的那一个单元的那一列之后才成立, 当然, 也只对目前这个单元有效.

由于自订的条件符号只有区域的作用, 如果有好几个程序单元都需要参考到某一个条件符号, 怎么办呢? 嗯! 在-一个程序单元开头处中都加上编译指示是最直接的方式, 可是略嫌麻烦, 特别是编译指示有变时, 要一一修正各个单元的设定内容, 很容易因为疏忽而出错。比较简易可行的作法是从Delphi IDE整合发展环境的主选单-Project / Options/ Directories/Conditional的Conditionals 中填入条件名称。这样, 相对于项目的各个unit而言, 就有了一个全域的条件符号。

或者, 您也可以参考本文对于{$I}这个Compiler Directive的说明。我在那里指出了另一个弹性的解决方式。

修改过编译指令后, 建议Build All过一次程序.

请试一试这个程序:

procedure TForm1.Button1Click(Sender: TObject);

begin

// ifopt是用来侦测某一个编译开关的作用状态

{$ifopt H+}

ShowMessage(''H+'');

{$else}

ShowMessage(''H-'');

{$endif}

end;

在我们执行上述程序时, 在Delphi预设的是$H+时, ShowMessage()会在画面上会显示「H+」, 执行过后, 让程序与form的内容与位置保留不变, 单纯的从主选单:

Project/Options/Compiler, 将Huge Strings的核对方块清除($H-), 然后按下F9执行,咦! 怎么还是看到「H+」?!

那是因为Delphi只会在unit内容经过异动后才会重新将.PAS编译成.DCU, 在我们的例子中, 程序并没有变动, .DCU当然也没有重新产生, 最后.EXE的这个部分自然也是没什么变化。

所以, 要解决这个问题, 只要以Delphi IDE主选单Project/Build All指示Delphi重新编译全部的程序即可。因此, 如果您从Delphi IDE修改过Compiler Directives后, 记得要Build All喔!

不应该用来作为程序执行流程控制

在程序中, 我们可以使用if叙述, 根据执行当时的情况控制程序执行时的流程, 但我们不可以用{$IFDEF}来作同样的事, 为什么? 从上述的说明, 相信您不难发现, Compiler directives会对最后.EXE的内容发生直接的影响, 应用像是{$IFDEF}指示Compiler的结果, 几乎可以视同授权Compiler在编译的那个时候自动选用/舍弃程序到.DCU, .EXE 中, 换句话说, 在程序编译完成时, 会执行到那一段程序已成定局了, 我们自然不能用它来作程序流程的控制。

条件编译的巢套最多可以16层

在使用{$IFDEF}#{$ENDIF}条件编译我们的程序时, 一个{$IFDEF}中可以再包含另

一个{$IFDEF}, 但深度最多只能16层, 虽然是个限制, 但以正常的情形来说, 这应该已经足够了。

有些Compiler directives不应写在Unit中

对于像是{$MINSTACKSIZE}{$MAXSTACKSIZE}管理堆栈大小, 或者像是{$APPTYE}指示程序编译成图形/文字模式的Compiler directives, 只能写在.DPR中, 写在Unit中是没有效果的。

建议事项

确定您了解指令的影响

由于编译指令的影响是如此直接与深远, 在修改与应用某一个Compiler directive时, 请确定您已经了解其含意与影响。

打开全部的侦错开关

Delphi有关侦错的Compiler directives如下:

$HINTS ON

$D+

$L+

$Q+

$R+

$WARNINGS ON

各指令的用法您可以参阅本章稍后对个别指令的说明, 全部打开这些开关吧! 这样不仅让

您可以使用Delphi IDE的除错器, 对于程序编译与执行过程中的问题, Delphi也会适时的反应, 有助于写作正确的程序。

此处有一个迷思有待澄清-「加入Dubug信息会不会让执行文件变大变慢啊?」, 不一定。

对于们像是$D+, $L+, $HINTS ON这些开关, 打开后, Delphi在编译时的确会额外加入一些除错信息, 使得.DCU的档案变大, 对于.EXE的档案大小并没有影响; 同时, 程序的执行速度也没有改变, 还可以应用IDE的除错器trace我们的程序, 值得应用。

对于像是$Q, $R等Compiler directive, 的确会影响执行文件的大小与速度, 然而这并不动摇我们在研发期间使用它们的决定, 请想想看, 值得为这一点点的速度放弃程序的正确

性吗? 当然, 程序开发完成后, 正式出货的版本, 可以关闭这两个开关。

如果您写好了一个组件, 而且只预备提供.DCU, 由于没有.PAS可供Delphi IDE的Debugger追踪程序, 除错开关反而应该在组件脱手前关闭并重新编译.DCU, 否则会引起使用者那边找不到档案的例外讯息。

善用{$I}

{$I FileName}是一个非常有用的Compiler directive.应用这个指令, 我们可以弹性的管理Compiler directive的设定。

条件名称请加入前导符

不知道您有没有这个疑问-- 如果用{$DEFINE}定义的条件名称与变量名称相同时会发

生什么事?

procedure TForm1.Button1Click(Sender: TObject);

var

TEST: integer;

begin

{$DEFINE TEST}

{$IFDEF TEST}

ShowMessage(''Test'');

{$ENDIF}

end;

以上的程序编译与执行都没有问题, 但条件名称与变量名称重复毕意容易让人混淆, 因此, 假如能适当的为编译条件名称之前加上诸如底线(_TEST), 程序会比较容易阅读。

设定一致的编译环境

在您了解了Compiler Directives之后, 请立即开始着手修改您IDE中有关编译指示的各个开关并且设为Default, 这样, 日后您的项目乃至整个研发小组都将拥有共同一致的编译环境, 对于写出来的程序会以何种方式编译连结都了然于胸, 直接有助于子系统顺利并入

主系统中。

个别指令说明

有了之前对于Compiler directives的观念之后, 接下来的这一节我将一一介绍几个常用的Compiler Directive的用法与注意事项, 您可以从这一节中学到更多有关Compiler directives的知识与使用细节。

{$A+} 字段对齐

在{$A+}(默认值)的情形下, 如果没有使用packed 修饰词宣告的record 型态,其字段会以CPU可以有效存取的方式向1. 2. 4 等边界对齐, 以获取最佳的存取速度。以下列的程序示例来说:

{$A+}

type

MyRecord = record

ByteField: byte;

IntegerField: integer;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

ShowMessage(IntToStr(SizeOf(MyRecord)));

end;

ShowMessage在{$A+}时显示的结果是:「8」; 倘若是{$A-}, 那所得的结果是「5」, 按理说, Byte应该只要一个byte就足够了, 但是考虑到硬件的执行特性, 经过对齐后的record会有比较好的执行速度。

有关这个Compiler Directive要注意的事项是: 不管{$A}的开关是ON或OFF, 使用packed修饰过的记录宣告, 是一定不会对齐的. 例如:

MyRecord = packed record // 不会对齐的记录宣告方式

{$APPTYPE GDI} 应用程序型态

一般的情形下, Delphi会以{$APPTYPE GUI}的方式产生一个图形的使用者接口程序, 如果您需要产生一个文字屏幕模式的程序, 那可以经由:

在.DPR中加入{$APPTYPE CONSOLE}

从主选单: Project/Options/Linker/EXE and DLL Options, 核取「Generate Console

Application」Check Box。

其它有关这个Compiler Directive的注意事项有:

$APPTYPE不能应用在DLL的项目或单一的程序单元(Unit), 它只对.EXE有意义。而且只有写在.DPR中才有作用。

我们可以应用System程序单元中的IsConsole函数在程序执行时侦测应用程序的类型。参阅Object Pascal手册第十三章可以知道更多有关Console Mode Application的信息。

{$B-} 布尔评估

请看以下的程序:

if (Length(sCheckedDateString) <> 8)

or EmptyStr(sCheckedDateString)

or (sCheckedDateString = '' . . '')

or (sCheckedDateString = '' / / '') then

begin

Result := True;

Exit;

end;

假如sCheckedDateString的字符串内容是「85/12/241」(长度9)的话, 以上的if述句, 其实在第一个逻辑判断时就已经知道结果了, 即使不看后来的逻辑运算结果也知道整个式

子会是真值。

假如您希望对整个逻辑表达式进行完整的评估-- 尽管结果已知, 后来的逻辑运算也不影

响整个的结果时仍要全部评估过, 请将这个Compiler directives设为{$B+}, 反之, 请设为{$B-}, 系统的默认值是{$B-}。

{$D+} 除错信息

当程序以{$D+}(默认值)编译时, 我们可以用Delphi整合发展境境的Debugger设定断点, 也可以使用Trace Into或Trace Info追踪程序的执行过程, 值得注意的是, 以{$D+}编译的程序, 执行的速度并不会受到影响, 只不过编译过的DCU的档案长度会加大, 但EXE档的大小不变。

{$DEFINE条件名称} 定义条件名称

随着您对Compiler Directives的了解与应用程度的加深, 您会发现这是一个非常实用的编译指示。

经常, 我们会因为除错需要﹑区别不同版本等缘故, 希望选择性的采用或排除某一段程序, 这个时候, 我们就可以先以$DEFINE定义好一个条件名称(Conditional name), 然后配

合{$IFDEF条件名称}#{$ELSE}#{$ENDIF}指示编译器按指定的条件名称之有无来选择需要编译的程序。

以下列的程序片断来说:

{$DEFINE _ProVersion}

procedure TForm1.Button1Click(Sender: TObject);

begin

{$IFDEF _Proversion}

frmPrint.ShowModal; // A

{$ELSE}

ShowMessage(''很抱歉, 试用版不提供打印功能'');

{$ENDIF}

end;

编译器将会选择编译上述A的那列程序, 日后, 如果我们需要编译「简易版」的程序版本时, 只要:

将{$DEFINE _ProVersion}那列整个删掉。

或者, 将{$DEFINE _ProVersion}改成{-$DEFINE _ProVersion}, 让它变成普通的批注

或者, 在{$DEFINE _ProVersion}的下一列加上{$UNDEF _ProVersion}, 解除

_ProVersion这个条件名称的定义。

这样, 由于_ProVersion这个条件名称未定义的缘故, Compiler就只会选择{$ELSE}下的那段程序, 重新编译一次, 不需费太多力气, 很容易的就可以制作出「简易版」了, 省去了要同时维护两份程序的麻烦。

使用$DEFINE时的其它注意事项如下:

以{$DEFINE}定义的条件名称都是区域的。换句话说, 它的作用范围只在当时所在的单元才有效, 即使定义在unit的interface, 由其它的unit以uses参考也没有效, 仍然只有在目前的unit有作用。

此外, 它的作用范围是从定义起, 到unit结尾或者以{$UNDEF}解除为止。

如果程序单元中已经用{$DEFINE}定义了一个条件名称, 而且也没有用{$UNDEF}解除定义, 重新{$DEFINE}一个同样名称并没有作用, 换句话说, 它们是同一个.

假如需要一个全域的条件名称, 您可以:主选单: Project / Options /

Directories/Conditional 的Conditionals 中填入条件名称。

以下的标准条件名称, 是Delphi 2.0已经预先预备好的, 我们可以直接引用, 同时, 它们都是全域的, 任何Unit都可以参照得到。

VER90: Delphi Object Pascal的版本编号。90表示9.0版, 日后若出现9.5版时, 也会有VER95的定义。

WIN32: 指出目前是在Win32(95, NT)作业环境

CUP386: 采用386(含)以上的CPU时, 系统会提供本条件名称。

CONSOLE: 此符号会于应用程序是在屏幕模式下编译时才定义。{$DESCRIPTION 描述内容}

应用{$DESCRIPTION}可以指定加入一段文字到.EXE或.DLL表头的模块描述进入点(module description entry)中﹐通常我们会用这个Compiler Directive加入应用程序的名称与版本编号到.EXE中。

例如:

{$DESCRIPTION Dchat Version 1.0}

{$X+} 扩充语法

这是为了与之前的Pascal版本前向兼容的编译指令, 虽然设定这个开关型的指令仍有作用, 但笔者建议您大可保留系统的默认值{$X+}, 在{$X+}下:

不需要非得准备一个变量接受函数的传回值, 换句话说, 函数的传回值可以舍弃, 此时, 就可以像是呼叫程序一样, 很方便的呼叫函数。

支持Pchar型态与零基的字符数组作为C语言以Null结尾的字符串。

{$HINTS OFF} 提示讯息

打关{$HINTS}开关后, Compiler会提示程序设计师注意以下的情况:

变量定义了却没有使用

程序流程中不会执行的for或while循环

只有存入没有取用的指定叙述。意思是说, 指定数据到某一个变量之后, 却没有任何的程序参考取用这个变量值。

{$HINTS ON}

procedure MyTest;

const _False = False;

var

I, J: integer;

begin

if _False then

for I := 1 to 3 do ;

J := 3;

end;

{$HINTS OFF}

由于程序简单, 在两个$HINTS中间的程序, 我们不难看出:

for循环不会执行到, I变量也因此不曾用过

J := 3写了等于白写

但在程序越写越长而日趋复雓时, 藉由{$HINTS ON}的协助, 比较容易察觉出程序的毛病。

{$IFDEF} {$IFNDEF}

请参阅{$DEFINE}的说明, 在此补充说明{$IFNDEF}, 以下列程序来说, 即在指示Compiler在_Test未定义时, 条件编译ShowMessage()那列程序:

{$IFNDEF _TEST}

ShowMessage(''_TEST not define'');

{$ENDIF}

换言之, {$IFNDEF}相当于{$IFDEF}的{$ELSE}部分。

{$IFOPT 开关}

到底{$B}是开着或关着呢? 如果我们想要指示Compiler按照某一个编译开关当时的状态作我们指定的事, 应该该怎么做呢? 这时, {$IFOPT}就派得上用场了。例如:

{$R+}

{$Q-} // 特别指定为Q-

{$IFOPT R+} // 如果Range Check 是开启的话

ShowMessage(''程序是在Range Check 开启状态下编译的''); // 这个Q+ 也会在IFOPT R+ 成立时才通知Compiler

{$Q+}

{$ENDIF}

{$IFOPT Q+}

ShowMessage(''Q 也变成开启状态了'');

{$ENDIF}

ShowMessage() 与{$Q+}会在$R+ 的情形下才编译, 因此, 虽然我们事前特别指示为{$Q-}, 第二个的ShowMessage()在程序执行时也可以看到「Q 也变成开启状态了」。{$IMAGEBASE 档案基础地址}

这个Compiler directive用来指示.EXE或.DLL加载时的预设地址。例如: {$IMAGEBASE $00400000}。如果指定加载的地址空间之前已经有其它模块占用了, Windows会为.EXE重新配置一个新的加载地址。对于.DLL来说, 如果可以成功配置到我

们写在{$IMAGEBASE}的地址, 由于不需要重新配置内存地址, 不仅加载的速度较快, 如果有其它程序也参照到这个DLL的话, 也可以减少加载时间与内存的消耗。

使用这个Compiler directive时需要注意的事项有:

指定的叙述必须是一个大于$00010000的32位整数数值, 同时, 较低位置的16个位必须是零。

DLL的建议地址范围从$40000000到$7FFFFFFF, 该范围的地址可以同时适用于Windows 95与Windows NT。

{$I文件名称} 含入档案

以Delphi IDE修改Compiler directives的确相当方便, 但往往我们仍然需要将Compiler directives直接加入程序中, 可是当我们这样作之后不用多久, 就会发现要一一重新修改各个单元中的这些Compiler directives时, 实在是既无聊而又容易出错的工作。这时候, 假如您一开始就采用{$I文件名称}, 整件事就会变得很简单。怎么做呢? 让我用一个例子告诉您--

先用一般的文书编辑器建好一个MySet.inc的普通文本文件, 内容为:

{$H+}

{$DEFINE _Proversion}

在我们的程序中, 加入一列{$I MySet.inc}, 例如:

unit Unit1;

{$I MySet.inc}

interface

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);

begin

{$IFDEF _ProVersion}

ShowMessage(''专业版'');

{$Else}

ShowMessage(''只有专业版才有此功能'');

{$ENDIF}

end;

这是子程序的观念嘛! 没错, 就是这么简单而已, 以后如果有任何变化, 修改MySet.INC, 然后Project/Buile All即可, 实在是够简单的了。

基本动作会了之后, 让我告诉你多一点有关{$I文件名称}的事。

一旦应用了{$I文件名称}, 几乎等于Compiler在编译时, 让Compiler将这个档案的内容贴进我们的程序中的那个位置。

如果没有注明扩展名, Delphi预设这个档案是.PAS。

如果在项目的目录中找不到这个档案的话, Delphi会陆续搜寻Tools/Options/Library中的Library Path中的目录。

另外, 当您写作了一个DLL, 使用者在使用其中的函数前必须宣告过, 如果能够一并提供这些函数的宣告文件, 使用者只要一行{$I xxx}即可, 是不是很方便呢?

{$I+} EInOutError检查

在{$I+}(系统默认值)状态编译的程序, 一旦发生I/O错误时, 将会举发一个EInOutError的例外, 假如我们在特定的情况下不希望出现这个例外的讯息时(例如前文提到的侦测档案是否存在函数), 可以将这个Compiler directive设为{$I-}, 此时, 程序执行时是否发生过错误,程序设定师必须自行检查IOResult这个公用变数的值, 如果是零, 表示没有错误, 非零的错误代码含意请详查Online help。

{$L文件名称} 连结目标文件

如果您有一个.OBJ文件要并入Delphi的程序时, 可以在程序中加入:

{$L OTHER.OBJ}

这样, 就可以使用OTHER.OBJ中的程序了, 值得注意的是, 函数或程序在呼叫前,仍然必须用external宣告过, 表明这些模块是来自「外部」的函式。

举例来说, 笔者有一份由Keypro厂商提供的.OBJ档, 在使用时, 相关的程序如下:

{$L hasptpw.obj}

{$F+}

procedure hasp (Service, SeedCode, LptNum, Pass1, Pass2 : word;

var p1,p2,p3,p4 : word); external;

{$F-}

经过{$L hasptpw.obj}宣告之后, 程序的其它部分就可以直接呼叫原先位于hasptpw.obj中的hsap这个程序了。

{$L+} 区域符号信息

在{$L+}时, Delphi会额外加入一些区域符号信息, 这使得我们可以应用Delphi IDE中的View/Call Stack, View/Watch在程序执行时检视变量内容与函式呼叫的关系。

应用这个Compiler directive的注意事项有:

{$D-}时, {$L+}不会有作用。

使用{$L+}, 只会加大.DCU的档案大小, 对.EXE的大小与执行速度并没有影响。{$H+} 长字符串宣告

Delphi 2.0之后, 字符串多了一个更为好用的长字符串, 不仅没有数据长度255的限制与C语言惯用的Null-terminated string兼容性也大为提高。

使用{$H}时的注意事项有:

{$H+}的编译情形下, 以string定义的字符串变量都是长字符串, 请注意, 字符串是否为长字符串是在字符串定义时决定的, 例如:

procedure TForm1.Button1Click(Sender: TObject);

{$H-}

var

s: string;

begin

{$H+}

s := ''测试一下长字符串'';

Windows.MessageBox(0, pchar(s), ''讯息'', 64);

end;

由于var前{$H-}的缘故, 虽然在begin后我们立即设定为{$H+}, 但s仍然是一个短字符串, 所以, 自然不能像是长字符串一样, 以pchar强制型别转换后当作

Null-terminated字符串使用。

承上, 不管程序是{$H+}或{$H-}, 只要字符串是以长字符串方式定义的, 即使begin..end;中改成{$H-}, 该字符串的操作仍然具有长字符串的特性。

因此, 由于VCL中的字符串都是长字符串, 即使我们的程序是{$H-}, 仍然可以拿它们当长字符串来使用。

不论{$H}的状态如何, 以AnsiString定义的一定是长字符串; 以string[n]或ShortString定义的一定是短字符串。

{$M 16386, 1048576} 内存配置大小

要改变唯迭(Stack)内存配置大小时, 我们可以有以下两种选择:

使用{$MINSTACKSIZE数字}, {$MAXSTACKSIZE数字}, 分别指定最小.最大的Stack大小.

或者使用{$M min, max}, 同时指定最小与最大的值。

使用这些Compiler directive时的注意事项有:

写在.DPR中才有效果。

堆栈的最小数字必须介于1024至21474835647之间。

堆栈的最大数字必须介于$MINSTACKSIZE至21474835647之间。

当内存不足而无法满足最小的堆栈大小时, Windows会在启动这程序时提出错误报告。

当程序要求的内存超过$MINSTACKSIZE的大小时, 将举发EStackOverflow例外。{$Z1} 最小列举大小

这个Compiler directive将影响储存列举型态时最小所需的byte数值。如果宣告列举型态时, 数值不大于256, 而且也在系统预设的{$Z1}时, 这个列举型态只占用一个byte储存的。{$Z2}时, 以两个byte储存, {$Z4}时, 以四个byte储存。因为C语言通常以WORD或DWORD储存列举型态, 如果您的程序需要与C、C++沟通时,{$Z2}{$Z4}就很管用了

{$Z+}, 与{$Z-}分别对应到{$Z1}和{$Z4}。

{$P+} 开放字符串参数

在程序与函数宣告时, 其中的字符串自变量, 在{$P+}时表示是Open string; {$P-}时, 只是一般的字符串变量而已。这个Compiler directive只在{$H-}时有作用。

{$O+} 最佳化开关

建议您维持{$O+}的系统默认值。开启这个Compiler directive, Delphi会自动进行最佳化处理, 程序可以因此跑得快一些, 您可以放心的打开这个编译开关, Delphi不会进行不安全的最佳化而使您的程序执行时发生错误。

{$Q-} 满溢检查, {$R-} 范围检查

{$Q}与{$R}是一组搭配使用的Compiler directive, 它们将检查数值或数组的操作是否在安全的边界中, {$Q}会检查整数运算(如+, -, Abs, Sqr, Pred, Succ等), 而{$R}则检查字符串与数组的存取是否超出合理边界范围等问题。

使用这两个Compiler directives会因为这些检查动作而降低程序执行的速度, 通常我们会在除错时开启这两个编译开关。

{$U-} Pentium CPU浮点运算安全检查

还记得早期Pentium CPU浮点运算不正确的事吧? 这批CPU应该回收得差不多了, 但如果您仍然不确定程序会不会意外的遇到漏网之鱼或黑心牌经销商的话, 请将这个Compiler directives设为{$U+}。

根据Borland手册的说明, 如果CPU是没有暇疵的, 设定{$U+}对于执行速度只有轻微的影响; 但如果是问题CPU, 浮点的除法速度会因此慢上三倍, 是否要打开这个开关, 您心中应该已有取舍。

{$R文件名称} 资源档

在您还没有开始学习Compiler directives之前, 这个指令就已经出现在您的程序中了,-

次开出一个新的form时, Delphi自动在Implement开头部分中加入{$R *.DFM}, 在Project/Source中看到的.DPR程序中也有{$R *.RES}, 这些是什么意思呢? 意思是说, 在编译连结时, 含入与项目主档名同名的.RES, 以及与form unit档案同名的.DFM等资

源档。

如果您需要在程序中使用额外的资源(例如: 自订鼠标指针), 请注意不要自行以Resouse WorkShop或Image Editor等资源编辑器更改这些与Project或Form同名的资源档, 改变这些同名的档案不仅无效, 可能还有不可预期的错误。因些, 您应该在另外一个资源档中存放这些资源, 并于{$R}中写明档案的名称将其连结进来, 例如:

{$R MyCursor.res}

{$T-} @指针型态检查

应用@操作数可以取得变量的地址, 在{$T-}时, 以@取得是一个无型别的指标(Pointer)。反过来说, 在{$T+}时, 是有型别的指标, 假定I是一个integer的变量,@I所得到的即是相当于^Integer(Pointer of Integer)的指标。

{$WARNINGS ON} 编译器警告

这个Compiler directive与{$HINTS}的作用类似, 同样会对程序的可能问题提出警告。不同的是, 在{$WARNINGS ON}时, Compiler会对未初始化的变数、没有传回值的函数、建构抽象对象等情况提出警告。

{$J-} 型态常数只读

从前笔者曾经对以下的程序产生过疑惑:

{$J+}

procedure TForm1.Button1Click(Sender: TObject);

const

VarConst: integer = 4;

begin

VarConst := 5;

ShowMessage(IntToStr(VarConst));

end;

const不是常数吗? 为什么可以改呢? 在先前的Pascal版本中, 以const VarName: DataType = const value; 定义的具型态常数的确是可以改的, 假如您希望常数就是常数, 它不应该允许修改, 请将这个Compiler directive设为{$J-} 不论是{$J+}或{$J-}, 以const VarName = const value; 定义的常数(没有加上型别宣告), 是一个真正的常数,

其它的程序不可以改变其内容。

其实{$J+}时还有一个妙用, 那就是宣告出类似C语言static的变量, 换句话说, 产生了一个与Application相同生命周期的变量。在这种情形下, 变量只在第一次使用时才会建立, 函数或程序结束时, 该变量也不会消灭, 下一次再呼叫到这个函数或程序时, 我们仍然可

以参考到上次执行结束时的值。让我们试一下这个例子:

{$J+}

procedure TForm1.Button1Click(Sender: TObject);

const

i: integer = 0;

begin

ShowMessage(IntToStr(i));

Inc(i);

ShowMessage(IntToStr(i));

end;

第一次执行时, 我们分别会看到「0」「1」, 再点一次这个按钮时, 看到的将是「1」「2」。

KEIL常见编译错误大全

KEIL常见编译错误大全 【致命错误】 立即终止编译这些错误通常是命令行指定的无效选项的结果当编译器不 能访问一个特定的源包含文件时也产生致命错误 致命错误信息采用下面的格式 C51FATAL-ERROR– ACTION: LINE: ERROR: C51TERMIANTED. C51FATAL-ERROR– ACTION: FILE: ERROR: C51TERMIANTED. 下面说明Action和Error中可能的内容 Actions ALLOCATING MEMORY 编译器不能分配足够的存储区来编译指定的源文件. CREATING LIST-FILE/OBJECT-FILE/WORKFILE 编译器不能建立列表文件,OBJ文件,或工作文件这个错误的出现可能是磁盘满或写保护,或文件已存在和只读. GENERATING INTERMEDIATE CODE 源文件包含的一个函数太大,不能被编译器编译成虚拟代码.尝试把函数分小或重新编译. OPENING INPUT-FILE 编译器不能发现或打开所选的源或包含文件. PARSING INVOKE-/#PRAGMA-LINE 当在命令行检测到参数计算,或在一个#pragma中检测到参数计算,就产生这样的错误. PARSING SOURCE-FILE/ANALYZING DECLARATIONS 源文件包含太多的外部参考.减少源文件访问的外部变量和函数的数目. WRITING TO FILE

C语言习题集(预处理命令篇)

第六章预处理命令 6.1 选择题 1.下面叙述中正确的是()。 A. 带参数的宏定义中参数是没有类型的 B. 宏展开将占用程序的运行时间 C. 宏定义命令是C语言中的一种特殊语句 D. 使用#include命令包含的头文件必须以“.h”为后缀 2.下面叙述中正确的是()。 A. 宏定义是C语句,所以要在行末加分号 B. 可以使用#undef命令来终止宏定义的作用域 C. 在进行宏定义时,宏定义不能层层嵌套 D. 对程序中用双引号括起来的字符串内的字符,与宏名相同的要进行置换 3.在“文件包含”预处理语句中,当#include后面的文件名用双引号括起时,寻找被包含文件的方式为()。 A. 直接按系统设定的标准方式搜索目录 B. 先在源程序所在目录搜索,若找不到,再按系统设定的标准方式搜索 C. 仅仅搜索源程序所在目录 D. 仅仅搜索当前目录 4.下面叙述中不正确的是()。 A. 函数调用时,先求出实参表达式,然后带入形参。而使用带参的宏只是进行简单的 字符替换 B. 函数调用是在程序运行时处理的,分配临时的内存单元。而宏展开则是在编译时进 行的,在展开时也要分配内存单元,进行值传递 C. 对于函数中的实参和形参都要定义类型,二者的类型要求一致,而宏不存在类型问 题,宏没有类型 D. 调用函数只可得到一个返回值,而用宏可以设法得到几个结果 5.下面叙述中不正确的是()。 A. 使用宏的次数较多时,宏展开后源程序长度增长。而函数调用不会使源程序变长 B. 函数调用是在程序运行时处理的,分配临时的内存单元。而宏展开则是在编译时进 行的,在展开时不分配内存单元,不进行值传递 C. 宏替换占用编译时间 D. 函数调用占用编译时间 6.下面叙述中正确的是( )。 A. 可以把define和if定义为用户标识符 B. 可以把define定义为用户标识符,但不能把if定义为用户标识符 C. 可以把if定义为用户标识符,但不能把define定义为用户标识符 D. define和if都不能定义为用户标识符 7.下面叙述中正确的是()。 A.#define和printf都是C语句 B.#define是C语句,而printf不是 C.printf是C语句,但#define不是 D.#define和printf都不是C语句

c语言中预编译指令的应用

#if #ifdef和#ifndef的用法和区别 #if #ifdef和#ifndef用法 移位运算符的优先级高于条件运算符,重载是不能改变运算符优先级的,这点要注意,所以代码应当像下面这样调整,写宏的时候一定要注意优先级,尽量用括号来屏蔽运算符优先级。#define MAXIMUM(x,y) ((x)>(y)?(x):(y)) #define MINIMUM.... #include #define MAX #define MAXIMUM(x,y) x>y?x:y #define MINIMUM(x,y) x

常见C语言错误提示信息

Ambiguous operators need parentheses 不明确的运算需要用括号括起Ambiguous symbol ''xxx'' 不明确的符号 Argument list syntax error 参数表语法错误 Array bounds missing 丢失数组界限符 Array size toolarge 数组尺寸太大 Bad character in paramenters 参数中有不适当的字符 Bad file name format in include directive 包含命令中文件名格式不正确 Bad ifdef directive synatax 编译预处理ifdef有语法错 Bad undef directive syntax 编译预处理undef有语法错 Bit field too large 位字段太长 Call of non-function 调用未定义的函数 Call to function with no prototype 调用函数时没有函数的说明 Cannot modify a const object 不允许修改常量对象 Case outside of switch 漏掉了case 语句 Case syntax error Case 语法错误 Code has no effect 代码不可述不可能执行到Compound statement missing{ 分程序漏掉"{" Conflicting type modifiers 不明确的类型说明符 Constant expression required

要求常量表达式 Constant out of range in comparison 在比较中常量超出范围Conversion may lose significant digits 转换时会丢失意义的数字Conversion of near pointer not allowed 不允许转换近指针 Could not find file ''xxx'' 找不到XXX文件 Declaration missing ; 说明缺少";" Declaration syntax error 说明中出现语法错误 Default outside of switch Default 出现在switch语句之外Define directive needs an identifier 定义编译预处理需要标识符Division by zero 用零作除数 Do statement must have while Do-while语句中缺少while部分Enum syntax error 枚举类型语法错误 Enumeration constant syntax error 枚举常数语法错误 Error directive :xxx 错误的编译预处理命令 Error writing output file 写输出文件错误 Expression syntax error 表达式语法错误 Extra parameter in call 调用时出现多余错误 File name too long 文件名太长 Function call missing ) 函数调用缺少右括号

IAR常见编译错误

【转】IAR常见编译错误—比较全 IAR常见编译错误,比较全面的: 前面的序号表示错误编号 0 Format chosen cannot support banking Format unable to support banking. 1 Corrupt file. Unexpected end of file in module module (file) encountered XLINK aborts immediately. Recompile or reassemble, or check the compatibility between XLINK and C compiler. 2 Too many errors encountered (>100) XLINK aborts immediately. 3 Corrupt file. Checksum failed in module module (file). Linker checksum is linkcheck, module checksum is modcheck XLINK aborts immediately. Recompile or reassemble. 4 Corrupt file. Zero length identifier encountered in module module (file) XLINK aborts immediately. Recompile or reassemble. 5 Address type for CPU incorrect. Error encountered in module module (file) XLINK aborts immediately. Check that you are using the right files and libraries. 6 Program module module redeclared in file file. Ignoring second module XLINK will not produce code unless the Always generate output (-B) option (forced dump) is used. 7 Corrupt file. Unexpected UBROF – format end of file encountered in module module (file) XLINK aborts immediately. Recompile or reassemble. 8 Corrupt file. Unknown or misplaced tag encountered in module module (file). Tag tag XLINK aborts immediately. Recompile or reassemble. 9 Corrupt file. Module module start unexpected in file file XLINK aborts immediately. Recompile or reassemble. 10 Corrupt file. Segment no. segno declared twice in module module (file) XLINK aborts immediately. Recompile or reassemble. 11 Corrupt file. External no. ext no declared twice in module module (file) XLINK aborts immediately. Recompile or reassemble. 12 Unable to open file file XLINK aborts immediately. If you are using the command line, check the

编译预处理

第九章编译预处理 9.1 选择题 【题9.1】以下叙述中不正确的是。 A)预处理命令行都必须以#号开始 B)在程序中凡是以#号开始的语句行都是预处理命令行 C)C程序在执行过程中对预处理命令行进行处理 D)以下是正确的宏定义 #define IBM_PC 【题9.2】以下叙述中正确的是。 A)在程序的一行上可以出现多个有效的预处理命令行 B)使用带参的宏时,参数的类型应与宏定义时的一致 C)宏替换不占用运行时间,只占编译时间 D)在以下定义中C R是称为“宏名”的标识符 #define C R 045 【题9.3】请读程序: #define ADD(x) x+x main() { int m=1,n=2,k=3; int sum=ADD(m+n)*k; printf(“sum=%d”,sum); } 上面程序的运行结果是。 A)sum=9 B)sum=10 C)sum=12 D)sum=18 【题9.4】以下程序的运行结果是。 #define MIN(x,y) (x)<(y)?(x):(y) main() { int i=10,j=15,k; k=10*MIN(i,j); printf(“%d\n”,k); } A)10 B)15 C)100 D)150 【题9.5】在宏定义#define PI 3.14159中,用宏名PI代替一个。 A)常量B)单精度数C)双精度数D)字符串

【题9.6】以下程序的运行结果是。 #include #define FUDGE(y) 2.84+y #define PR(a) printf(“%d”,(int)(a)) #define PRINT1(a) PR(a); putchar(‘\n’) main() { int x=2; PRINT1(FUDGE(5)*x); } A)11 B)12 C)13 D)15 【题9.7】以下有关宏替换的叙述不正确的是。 A)宏替换不占用运行时间B)宏名无类型 C)宏替换只是字符替换D)宏名必须用大写字母表示 【题9.8】C语言的编译系统对宏命令的处理是。 A)在程序运行时进行的 B)在程序连接时进行的 C)和C程序中的其它语句同时进行编译的 D)在对源程序中其它成份正式编译之前进行的 【题9.9】若有宏定义如下: #define X 5 #define Y X+1 #define Z Y*X/2 则执行以下printf语句后,输出结果是。 int a; a=Y; printf(“%d\n”,Z); printf(“%d\n”,--a); A)7 B)12 C)12 D)7 6 6 5 5 【题9.10】若有以下宏定义如下: #define N 2 #define Y(n) ((N+1)*n) 则执行语句z=2*(N+Y(5));后的结果是。 A)语句有错误B)z=34 C)z=70 D)z无定值 【题9.11】若有宏定义:#define MOD(x,y) x%y 则执行以下语句后的输出为。 int z,a=15,b=100; z=MOD(b,a); printf(“%d\n”,z++);

C中的预处理命令

C中的预处理命令是由ANSIC统一规定的,但它不是C语言的本身组成部分,不能直接对它们进行编译,因为编译程序无法识别它们。必须对程序进行通常的编译(包括词法和语法分析,代码生成,优化等)之前,先对程序中这些特殊的命令进行“预处理”,例如:如果程序中用#include命令包含一个文件“stdio.h”,则在预处理时,将stdio.h文件中的实际内容代替该命令。经过预处理后的程序就像没有使用预处理的程序一样干净了,然后再由编译程序对它进行编译处理,得到可供执行的目标代码。现在的编译系统都包括了预处理,编译和连接部分,在进行编译时一气呵成。我们要记住的是预处理命令不是C语言的一部分,它是在程序编译前由预处理程序完成的。 C提供的预处理功能主要有三种:宏定义,文件包含,条件编译。它们的命令都以“#”开头。 一,宏定义:用一个指定的标识符来代表一个字符串,它的一般形式为: #define 标识符字符串 #define PI 3.1415926 我们把标识符称为“宏名”,在预编译时将宏名替换成字符串的过程称为“宏展开”,而#define 是宏定义命令。 几个应该注意的问题: 1,是用宏名代替一个字符串,也就是做简单的置换,不做正确性检查,如把上面例子中的1写为小写字母l,预编译程序是不会报错的,只有在正式编译是才显示出来。 2,宏定义不是C语句,不必在行未加分号,如果加了分号则会连分号一起置换。 3,#define语句出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束,通常#define命令写在文件开头,函数之前,作为文件的一部分,在此文件范围内有效。4,可以用#undef命令终止宏定义的作用域。如: #define PI 3.1415926 main(){ } #undef PI mysub(){ } 则在mysub中PI 不代表3.1415926。 5,在进行宏定义时,可以引用已定义的宏名,可以层层置换。 6,对程序中用双撇号括起来的字符串内的字符,即使与宏名相同,也不进行置换。 7,宏定义是专门用于预处理命令的一个专有名词,它与定义变量的含义不同,只做字符替换不做内存分配。 带参数的宏定义,不只进行简单的字符串替换,还进行参数替换。定义的一般形式为:#define 宏名(参数表)字符串 如:#define S(a,b) a*b,具体使用的时候是int area; area=(2,3); 对带参数的宏定义是这样展开置换的:在程序中如果有带参数的宏(如area=(2,3)),则按#define命令行中指定的字符串从左到右进行置换。如果串中包含宏中的形参(如a,b),则将程序语句中的相关参数(可以是常量,变量,或表达式)代替形参。如果宏定义中的字符串中的字符不是参数字符(如上*),则保留,这样就形成了置换的字符串。 带参数的宏与函数有许多相似之处,在调用函数时也是在函数名后的括号内写实参,也要求实参与形参的数目相等,但它们之间还有很大的不同,主要有: 1,函数调用时,先求出实参表达式的值,然后代入形参,而使用带参的宏只是进行简单的字符替换。

第9章 预处理命令

第9章预处理命令 宏定义不是C语句,所以不能在行尾加分号。如果加了分号则会连分号一起进行臵换。 可以用#undef命令终止宏定义的作用域。 对程序中用“”括起来的内容(即字符串内的字符),即使与宏名相同,也不进行臵换。宏定义只做字符替换,不分配内存空间。 宏名不是变量,不分配存储空间,也不能对其进行赋值。 在宏展开时,预处理程序仅对宏名作简单的字符串替换,不作任何检查。 在进行宏定义时,可以引用已定义的宏名 无参宏定义的一般格式: #define 标识符字符串 将这个标识符(名字)称为“宏名”,在用预编译时将宏名替换成字符串的过程称为“宏展开”。#define是宏定义命令。 带参宏定义的一般格式: #define 宏名(形参表)字符串 带参宏的调用和宏展开: 调用格式:宏名(实参表); 宏展开(又称为宏替换)的方法:用宏调用提供的实参直接臵换宏定义中相应的形参,非形参字符保持不变。 定义有参宏时,宏名与左圆括号之间不能留有空格。否则,C编译系统会将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。 有参宏的展开,只是将实参作为字符串,简单地臵换形参字符串,而不做任何语法检查。 为了避免出错,可以在所有形参外,甚至整个字符串外,均加上一对圆括号。 如: #define S(r) 3.14*(r)*(r) 则:area=S(a+b); 展开后为: area=3.14*(a+b)*(a+b); 调用有参函数时,是先求出实参的值,然后再复制一份给形参。而展开有参宏时,只是将实参简单地臵换形参。函数调用是在程序运行时处理的,为形参分配临时的内存单元;而宏展开则是在编译前进行的,在展开时不分配内存单元,不进行值的传递,也没有“返回值”的概念。调用函数只可得到一个返回值,而用宏可以设法得到几个结果。 在有参函数中,形参都是有类型的,所以要求实参的类型与其一致;而在有参宏中,形参和宏名都没有类型,只是一个简单的符号代表,因此,宏定义时,字符串可以是任何类型的数据。 使用宏次数多时,宏展开后源程序变长,因为每展开一次都是程序增长,而函数调用不会使源程序变长。 宏替换不占用运行时间,只占编译时间。而函数调用则占用运行时间(分配单元、保留现场、值传递、返回)。 在程序中如果有带实参的宏,则按#define命令行中指定的字符串从左到右进行臵换。如果字符串中包含宏中的形参,则将程序语句中相应的实参(可以是常量、变量或表达式)代替形参。如果宏定义中的字符串中的字符不是参数字符,则保留。

编译时的常见错误

一、编译时的常见错误 1. 数据类型错误。此类错误是初学者编程时的常见现象, 下面是一些要引起注意的错误: (1) 所有变量和常量必须要加以说明。 (2) 变量只能赋给相同类型的数据。 (3) 对scanf()语句, 用户可能输入错误类型的数据项, 这将导致运行时出错, 并报出错信息。为避免这样的错误出现, 你就提示用户输入正确类型的数据。 (4) 在执行算术运算时要注意: a. 根据语法规则书写双精度数字。要写0.5, 而不是写.5; 要写1.0, 而不是1。尽管C语言会自动地把整型转换成双精度型, 但书写双精度型是个好习惯。让C语言为你做强行转换这是一种效率不高的程序设计风格。这有可能导致转换产生错误。 b. 不要用0除。这是一个灾难性的错误, 它会导致程序失败, 不管C 语言的什么版本, 都是如此, 执行除法运算要特别小心。 c. 确保所有的双精度数(包括那些程序输入用的双精度数) 是在实数范围之内。 d. 所有整数必须在整数允许的范围内。这适用于所有计算结果, 包括中间结果。 2. 将函数后面的";"忘掉。此时错误提示色棒将停在该语句下的一行, 并显示: Statement missing ; in function <函数名> 3. 给宏指令如#include, #define等语句尾加了";"号。 4. "{"和"}"、"("和")"、"/*"和"*/"不匹配。引时色棒将位于错误所在的行, 并提示出有关丢掉括号的信息。 5. 没有用#include指令说明头文件, 错误信息提示有关该函数所使用的参数未定义。 6. 使用了Turbo C保留关键字作为标识符, 此时将提示定义了太多数据类型。 7. 将定义变量语句放在了执行语句后面。此时会提示语法错误。 8. 使用了未定义的变量, 此时屏幕显示: Undefined symbol '<变量名>' in function <函数名> 9. 警告错误太多。忽略这些警告错误并不影响程序的执行和结果。编译时当警告错误数目大于某一规定值时(缺省为100)便退出编译器, 这时应改变集成开发环境Options/Compiler/Errors中的有关警告错误检查开关为off。 10. 将关系符"=="误用作赋值号"="。此时屏幕显示: Lvalue required in function <函数名> 二、连接时的常见错误 1. 将Turbo C库函数名写错。这种情况下在连接时将会认为此函数是用户自定义函数。此时屏幕显示: Undefined symbol '<函数名>' in <程序名> 2. 多个文件连接时, 没有在"Project/Project name中指定项目文件(.PRJ文件), 此时出现找不到函数的错误。 3. 子函数在说明和定义时类型不一致。 4. 程序调用的子函数没有定义。 三、运行时的常见错误 1. 路径名错误。在MS-DOS中, 斜杠(\)表示一个目录名; 而在Turbo C 中斜杠是个某个字符串的一个转义字符, 这样, 在用Turbo C 字符串给出一个路径名时应考虑"\"的转义的作用。例如, 有这样一条语句: file=fopen("c:\new\tbc.dat", "rb"); 目的是打开C盘中NEW目录中的TBC.DAT文件, 但做不到。这里"\"后面紧接的分别是"n"及"t", "\n"及"\t"将被分别编译为换行及tab字符, DOS将认为它是不正确的文件名而拒绝接受, 因为文件名中不能和换行或tab字符。正确的写法应为: file=fopen("c:\\new\\tbc.dat", "rb"); 2. 格式化输入输出时, 规定的类型与变量本身的类型不一致。例如: float l;

C#中的预处理指令

3.9.1排除和包含代码 或许最常用的预处理器指令就是用于控制什么时候以及如何包含代码的指令。举个例子来说,要使代码能够同时由C# 2.0和之前的C# 1.2版本编译器进行编译,可以使用一个预处理器指令,在遇到1.2编译器的时候,就排除C# 2.0特有的代码。我们的tic-tac-toe例子和代码清单 3-52对此进行了演示。 代码清单3-52遇到C# 1.x编译器的时候排除C# 2.0代码

在这个例子中,调用了System.Console.Clear()方法,这是只有2.0 CLI才支持的方法。使用#if和#endif预处理器指令,这一行代码就只有在定义了预处理器符号CSHARP2的前提下才会编译。 预处理器指令的另一个应用是适应不同平台之间的差异,比如用WINDOWS和LINUX #if指令将Windows和Linux特有的API包围起来。开发者经常用这些指令来取代多行注释 (/*...*/),因为它们更容易通过定义恰当的符号或者通过一次搜索/替换来移除。预处理器指令最后一个常见的用途是调试。如果用一个#if DEBUG指令将调试代码包围起来,那么在大多数IDE中,都能在最终的发布版本中移除这些代码。IDE默认将DEBUG符号用于调试编译,将RELEASE符号用于发布版本。 为了处理else-if条件,可以在#if指令中使用#elif指令,而不是创建两个完全独立的#if块,如代码清单3-53所示。 代码清单3-53使用#if、#elif和#endif指令 #if LINUX ... #elif WINDOWS ... #endif 输出3-28展示了在使用Mono编译器的前提下,如何实现相同的功能。

#pragma指令用法汇总和解析

#pragma指令用法汇总和解析 一. message 参数。 message 它能够在编译信息输出窗 口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: #pragma message(“消息文本”) 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条 指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法 #ifdef _X86 #pragma message(“_X86 macro activated!”) #endif 当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 二. 另一个使用得比较多的#pragma参数是code_seg。格式如: #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] ) 该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节 为.text节 如果code_seg没有带参数的话,则函数存放在.text节中 push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名 pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名 identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈 "segment-name" (可选参数) 表示函数存放的节名 例如: //默认情况下,函数被存放在.text节中 void func1() { // stored in .text } //将函数存放在.my_data1节中 #pragma code_seg(".my_data1") void func2() { // stored in my_data1 } //r1为标识符,将函数放入.my_data2节中 #pragma code_seg(push, r1, ".my_data2") void func3() { // stored in my_data2 } int main() { } 三. #pragma once (比较常用) 这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次 四. #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。

JAVA常见编译错误信息及说明

Java编译错误信息及说明 1、java:33:不是语句 解释:程序中出现了没有任何意义的字符(串),可能是无意中打出了没有任何意义的字符; 2、java:34:需要';' 解释:某条语句没有以分号结束; 3、java:36:缺少返回语句 解释:带返回值的函数缺少return语句; 4、java:33:不兼容的类型 解释:运算符两边的数据类型不一致或者不能做隐式转换; 5、java:36:无法访问的语句 解释:此语句永远不可能被执行,可能是此语句之前存在类似于while(true)的语句,导致此语句永远不可能被执行; 6、java:34:非法的表达式开始 解释:有不符合语法规范的表达式出现; 7、java:34:找不到符号 解释:使用了没有定义或没有引入的变量; 8、java:33:非法字符:\65307 解释:在中文输入状态下输入了一些标点符号; 9、java:18:不可转换的类型 解释:运算符两边的数据类型不一致或者不能做隐式转换; 10、java:19:"else"不带有"if" 解释:编译器找到else语句但是没有找到与之对应的if语句,可能是由于大括号没有成对出现;

11、java:12:可能损失精度 解释:把高精确度类型的数据赋值给低精确度类型的变量; 12、java:17:需要')' 解释:括号没有成对出现; 13、java:8:可能尚未初始化变量s 解释:局部变量s没有赋初值; 14、java:7:不可比较的类型:int和Boolean 解释:运算符两边操作数的数据类型不符合运算符的使用规范; 15、java:6:已在isLeap(int)中定义year 解释:变量year被重复定义; 16、java:21:字符字面值的行结尾不合法 解释:程序语句的结尾不是java规定的结束符号,而是其他的符号; 17、java:9:需要<标识符> 解释:可能是由于用户指定了数据类型,但未指定该类型的变量名; 18、java:8:无法从静态上下文中引用非静态变量this 解释:在静态方法中使用了非静态变量this; 19、java:12:在switch或loop外部中断 解释:在非循环语句或非switch语句中使用了中断循环功能的语句break; 20、java:21:对于结果类型为void的方法,无法返回值 解释:空返回值方法中出现了return语句; 21、java:12:需要数组,但找到int 解释:在应该出现数组的地方没有找到数组类型的变量,而是找到int类型的变量; 22、java:13:无法将Node中的setData(int)应用于()

c语言预处理命令之条件编译(ifdefelseendifif等)

C语言预处理命令之条件编译(#ifdef,#else,#endif,#if等) 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。 在C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。 预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令: 指令用途 #空指令,无任何效果 #include包含一个源代码文件 #define定义宏 #undef取消已定义的宏 #if如果给定条件为真,则编译下面代码 #ifdef如果宏已经定义,则编译下面代码 #ifndef如果宏没有定义,则编译下面代码 #elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 #endif结束一个#if……#else条件编译块 #error停止编译并显示错误信息 一、文件包含 #include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。

预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如: #defineAAA #include"t.c" #undefAAA #include"t.c" 为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如: /*my.h*/ #ifndefMY_H #defineMY_H …… #endif 在程序中包含头文件有两种格式: #include #include"my.h" 第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。 采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。

多文件结构和编译预处理命令

多文件结构和编译预处理命令 C++完整的源程序一般由三部分构成:类的定义,类成员的实现,主函数 在较大的项目中,常需要多个源文件(即多个编译单元),c++要求一个类的定义必须在使用该类的编译单元中。因此,把类的定义写在头文件中。 (重点)一个项目至少分三个文件:类定义文件(.h)、类实现文件(.cpp)、类的使用文件(.cpp) (重点)对于复杂的程序:每个类都有单独的类定义和类实现。这样做的好处:可以对不同的文件进行单独编写、编译,最后链接,同时利用类的封 装性,在程序的调试和修改时只对其中某一个类的定义和实现修改,其余保持不动。 预处理指令声明中出现的注释以及一行单独一个#符号的情况在预编译处理过程中都会被 忽略掉。 宏定义在c++中依然使用,但最好的方式是在类型说明语句中用const修饰来取代宏定义。(重点)大型程序中,往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以在头文件使用条件编译。(三种方式) 表1是所有预处理指令和意义 指令意义 #define 定义宏 #undef 取消定义宏 #include 包含文件 #ifdef 其后的宏已定义时激活条件编译块 #ifndef 其后的宏未定义时激活条件编译块 #endif 中止条件编译块 #if 其后表达式非零时激活条件编译块 #else 对应#ifdef, #ifndef, 或#if 指令 #elif #else 和#if的结合 #line 改变当前行号或者文件名 #error 输出一条错误信息 #pragma 为编译程序提供非常规的控制流信息 宏定义 #define指令定义宏,宏定义可分为两类:简单宏定义,带参数宏定义。 简单宏定义有如下一般形式: #define 名字替换文本 它指示预处理器将源文件中所有出现名字记号的地方都替换为替换文本,替换文本可以是任

C51几个预编译指令的用法

C51几个预编译指令的用法 标签: 指令用法编译2009-07-31 10:47 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。 在C语言中,并没有任何内在的机制来完成如下一些功能: 在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。 预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令: 指令用途 #空指令,无任何效果 #include包含一个源代码文件 #define定义宏 #undef取消已定义的宏 #if如果给定条件为真,则编译下面代码 #ifdef如果宏已经定义,则编译下面代码 #ifndef如果宏没有定义,则编译下面代码

#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 #endif结束一个#if……#else条件编译块 #error停止编译并显示错误信息 一、文件包含 #include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。 预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如: #define AAA #include "t.c" #undef AAA #include "t.c" 为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如: #ifndef MY_H #define MY_H …… #endif 在程序中包含头文件有两种格式: #include #include "my.h"

keil 编译错误提示

C51编译器识别错类型有三种 1、致命错误:伪指令控制行有错,访问不存在的原文件或头文件等。 2、语法及语义错误:语法和语义错误都发生在原文件中。有这类错误时,给出 提示但不产生目标文件,错误超过一定数量才终止编译。 3、警告:警告出现并不影响目标文件的产生,但执行时有可能发生问题。程序 员应斟酌处理。 D.1 致命错误 C_51 FATAL_ERROR ACTION: <当前行为> LINE: <错误所在行> ERROR: <错误信息> terminated 或C_51 FATAL ERROR ACTION: <当前行为> FILE: <错误所在文件> ERROR: <错误信息> terminated C_51 TERMINATED C_51 (1) ACTION 的有关信息 *PARSING INVOKE-/#PRAGMA_LINE 在对#pragma 指明的控制行作此法分析时出错。 *ALLOCATING MEMORY 系统分配存储空间时出错。编译较大程序需要512k空间。 *OPENING INPUT_FILE 打开文件时,未找到或打不开源文件/头文件。 *CREATE LIST_FILE/OBJECT_FILE/WORK_FILE 不能创建上述文件。可能磁盘满或文件已存在而且写保护。 *PARSING SOURCE_FILE/ANALYZING DECLARATIONS 分析源程序时发现外部引用名太多。 *GENERATING INTERMEDIATE CODE 源代码被翻译成内部伪代码,错误可能来源于函数太大而超过内部极限。 *WRITING TO FILE 在向文件(work,list,prelist或object file)写时发生错误。 (2)ERROR的有关信息 *MEMORY SPACE EXHAUSTED 所有可用系统空间耗尽。至少需要512k 字节空间。没有足够空间,用户必须检查常驻内存的驱动程序是否太多。 *FILE DOES NOT EXIST FILE 行定的文本文件名未发现。 *CAN’T CREAT FILE FILE 行定义的文件不能被创建。 *SOURCE MUST COME FROMA DISK_FILE 源文件和头文件必须存在于硬盘或软盘上。控制台、CON、CI 或类似设备不允许作为输入文件。 *MORE THAN 256 SEGMENTS/PUBLICS/EXTERNALS 受OMF_51的历史限制,一个源程序不能超过256个各种函数的类型段,256个全局变量,

C51几个预编译指令的用法

C51几个预编译指令的用法 标签: 指令用法编译2009-07-31 10:47 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。 在C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。 预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令: 指令用途 #空指令,无任何效果 #include包含一个源代码文件 #define定义宏 #undef取消已定义的宏 #if如果给定条件为真,则编译下面代码 #ifdef如果宏已经定义,则编译下面代码 #ifndef如果宏没有定义,则编译下面代码 #elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 #endif结束一个#if……#else条件编译块 #error停止编译并显示错误信息 一、文件包含 #include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。 预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如: #define AAA

相关文档