C和Delphi中的条件编译

4
下一个模式在C代码中很常见:
#ifndef SOMETHING
#define SOMETHING
#endif

该模式在Delphi代码中也是可能的:
{$IFNDEF SOMETHING}
{$DEFINE SOMETHING}
{$ENDIF}

但这并不常见 - 我根本没有见过。如果一个 Delphi 代码需要条件定义,它只需定义而不需要使用 IFNDEF 检查。

为什么呢?C 和 Delphi 之间的条件编译有什么区别,以至于前者需要 ifndef 检查而后者却不需要呢?


我认为 Delphi 的代码库比 C 小得多。 - Basile Starynkevitch
2个回答

12

这是因为这不仅在C语言中很常见,而且是强制性的:

#include <something.h>

虽然Delphi很少使用这个,但当被使用时,实际上是用来设置那些{$DEFINE}的:

{$INCLUDE 'something.inc'}

这很重要,因为宏定义(DEFINES)只在编译一个对象时有效(无论是.PAS文件还是.C文件)。Delphi使用uses语句来包含其他单元,而C使用include语句来包含头文件。在C中,头文件本身可能会包含其他头文件。你所询问的模式用于防止递归重新包含同一头文件。

为了让事情更加清晰明了,这里有一个在C中使用的示例以及相应的Delphi示例。假设我们有一个包含三个文件的设置,其中A需要包含BC,而B仅需要包含C。 "C" 文件如下:

// ----------------------- A.h
#ifndef A
#define A

#include "B.h"
#include "C.h"

// Stuff that goes in A

#endif

// ------------------------ B.h
#ifndef B
#define B

#include "C.h"

// Stuff that goes in B

#endif

// ----------------------- C.h
#ifndef C
#define C

// Stuff that goes in C

#endif

如果没有C.h中的条件定义,C.h文件将在A.h中被包含两次。以下是在Delphi中的代码:

// --------------------- A.pas
unit A;

interface

uses B, C;

implementation

end.

// --------------------- B.pas
unit B

interface

uses C;

implementation

end.

// --------------------- C.pas

unit C

interface

implementation

end.
Delphi/Pascal版本不需要保护"A"中的"C"重复被包含,因为它没有使用{$INCLUDE}来实现这个目标,而是使用uses语句。编译器将从B.dcu文件和C.dcu文件获取导出符号,没有包含C.dcu两次的风险。
C代码中看到更多预编译指令的其他原因:
  • 预编译器比Delphi的强大得多。在Delphi代码中,{$DEFINE}仅处理条件编译,而C变体可用于条件编译和作为一种单词替换形式。
  • 头文件的强制使用#include意味着您可以有一个定义宏的头文件。或者您可以通过在实际的#include <header.h>之前指定一些#define语句来配置头文件。

回答很详细,但您的示例不够 :-) 如果您从 C 到 A 添加一个依赖项,原因将更有意义。 - Christoffer
很好的解释如何使用#ifndef来防止头文件递归,但这似乎不是C语言中#ifndef的唯一用途。我已经看到很多次在没有后续#include的情况下使用#ifndef - kludg
从C到A的依赖关系可能更有趣,因为如果没有IFDEF,它会导致无限递归循环。但是,我会遇到Delphi示例的问题,因为Delphi不允许循环依赖。而且,我可能会遇到设计纯粹主义者的麻烦,因为循环依赖确实是一种不好的设计。 - Cosmin Prund
@Serg,在我的例子中,C.h文件没有任何INCLUDE,但是如果没有IFNDEF保护,它将在A.h中被包含两次。 - Cosmin Prund
我相信 #ifndef 不仅仅是用于防止头文件递归,尽管这可能是 #ifndef 最重要的用途。我还认为,在 C 语言中,#ifndef 的重要性与广泛使用头文件(*.h)有关,而它们的 Delphi 类似物(*.inc 文件)很少使用且目的非常有限。 - kludg
显示剩余12条评论

3

这种模式在C代码(.c或.cpp源文件)中并不常见。它在C/C++头文件(.h)中很常见:

#ifndef SOMETHING
#define SOMETHING
#endif

原因是为了防止同一个头文件在同一翻译单元中无意中包含多次。

例如,假设模块"a.c"使用了头文件"b.h",而"b.h" #include's "c.h"。这意味着"a.c"显式地使用了"b",隐式地也使用了"c"。目前为止,一切正常。

现在假设"c.h"也使用了"b.h"。"#ifndef/#define/#endif"的东西可以防止“b”被重复# include'd(一次在"a"中由"a",第二次在"c"从"a"中)。

在Delphi中这一切都是不必要的。Delphi中"$ifdef"仅用于条件编译;Delphi中的“单元”负责处理潜在的递归和/或循环依赖关系。


2
如果头文件不是代码,那它里面装的是什么?对我来说,C语言的头文件仍然是C代码。 - dreamlax
不要对“代码”一词争论不休。重要的是要区分“接口”(.h)和“实现”.cpp”。C/C++头文件使用#ifndef预处理器约定来减轻递归/循环定义。由于Pascal/Delphi使用单元的方式,因此不需要使用预处理器。 - paulsm4
PS:Pascal/Delphi(以及Delphi和其他语言)的一个非常好的特点是“接口”与“实现”的清晰、明确分离。 - paulsm4
我并不挑剔,接口和实现之间有区别,但我认为这两者都属于“代码”。 - dreamlax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接