【问题标题】:Why is preprocessor usage less common in languages other than C/C++/ObjC?为什么预处理器的使用在 C/C++/ObjC 以外的语言中不太常见?
【发布时间】:2010-11-19 03:08:39
【问题描述】:

我从事 Java 和 VB.Net 程序员大约 4 年,从事 C# 程序员大约 6 个月。我还使用了许多动态语言,例如 Perl、Python、PHP 和 JavaScript。

我从来不需要预处理器。

我的问题是:为什么您会在 C、C++ 和 Objective-C 中看到如此广泛地使用预处理器,但在 Java、C# 或 Scala 等语言中却很少(或从未)看到它?

【问题讨论】:

  • 我们从 C 中学习。使用 C/C++/Objective-C,他们得到了 C 编译器,然后就在它之上构建,因为这样更容易。最近的语言只是从头开始设计得更好。我认识的大多数具有 C 背景的人都对预处理器感到恐惧。不利于速度,搞砸你的设计,损害理解。除了让你编写更糟糕的代码之外没有任何优势——为什么有人会故意复制它?
  • 我有几个 C++ dsp 程序,每个周期都很重要(是的,真的)。预处理器在优化过程中帮助了我很多。我不将它用于其他任何事情。我喜欢它。你不能把它从我手里抢过来。你用那些动态语言做过很多实时 dsp 编程吗?请记住,无论您完成了多少种编程,您都只是触及了可以完成的各种编程的皮毛。
  • @Nosredna:哦,我完全同意,因为我不需要预处理器,所以我永远不需要它。我只是好奇为什么你看到它在 C 和 C 超集语言中的使用如此之多。
  • 我个人非常喜欢用于 C/C++ 测试的预处理器。我发现使用预处理器编写/扩展测试套件比用其他语言做类似的事情要容易得多。我个人在使用其他语言时会想念预处理器。 (当然,在日志记录中使用__FILE__/__LINE__ 和条件编译是我唯一需要使用预处理器的时候。)

标签: c# java c preprocessor


【解决方案1】:

我不了解 Objective-C,所以我的答案是对比 C 和 C++ 中预处理器的使用。

C 语言最初需要预处理器有几个原因。如果我没记错的话,最初 C 没有常​​量,所以需要 #define 来避免幻数。在 1999 年之前,C 没有内联函数,所以 #define 再次被用来创建宏或“伪函数”以节省函数调用的开销,同时保持代码的结构化。 C 也没有运行时或编译时多态性,因此条件编译需要#ifdefs。编译器通常不够聪明,无法优化掉无法访问的代码,因此再次使用#ifdefs 插入调试或诊断代码。

在 C++ 中使用预处理器是对 C 的倒退,通常不被接受。常量、内联函数和模板等语言特性可用于在 C 语言中使用预处理器的大多数情况下。

在 C++ 中使用预处理器是可以接受甚至是必要的少数情况包括对头文件的保护,以防止相同的头被多次包含,#ifdef __cplusplus 为两者使用相同的头C 和 C++,__FILE__ 和 __LINE__ 用于日志记录,以及其他一些。

预处理器也经常用于特定于平台的定义,尽管 Stephen Dewhurst 的 C++ Gotchas 建议为特定于平台的定义使用单独的包含目录,并在每个平台的单独构建配置中使用它们。

【讨论】:

  • 这不是唯一一次认为在 C++ 中使用预处理器是可以的。其他广泛(如果不是普遍)认为可以的用途包括在日志记录中输出__FILE__ 和__LINE__、Boost FOREACH 宏、检查编译器功能支持(包括经典的#ifdef __cplusplus // extern "C" {// #endif)、特定于平台的定义(例如,在哪里需要@ MSVC 中的 987654332@ 和 GCC 中的 __attribute__),在包含 ,... 之前定义 NDEBUG
【解决方案2】:

Java、C# 或 Scala 中没有使用预处理器的原因是这些语言显然没有。

C 预处理器的一个常见用途是帮助提供特定于平台的代码。由于 C(我在这里包括 C++ 和 Objective-C)是一种需要直接与操作系统交互的低级语言,因此在可移植代码中,必须为不同的操作系统编译不同的代码部分。您可以在诸如zlib 等成熟的、高度可移植的代码库中找到此类事情的大量示例。

作为一个简单的例子,要关闭一个网络套接字,必须做这样的事情(在某种程度上,这当然可以包装在一个函数中,但它必须存在于某个地方):

#ifdef WIN32
    closesocket(s);
#else
    close(s);
#endif

在 VM 上运行的新语言不需要特定于平台的不同代码部分,并且可以针对单一的可移植标准库编写。

预处理器还提供了一种在 C 中定义常量的方法,这些常量是由其他更好的语言特性在较新的语言中提供的。

Bjarne Stroustrup 在 The Design and Evolution of C++ 中表示,他想去除 C++ 中对预处理器的依赖,但没有成功。

【讨论】:

  • “你看不到预处理器的原因......是那些语言......没有。” !!太好了!
  • 真正的语言可以检查 WIN32 变量,看看它是最终的并生成正确的内联路径。现代语言中的预处理器几乎没有用处。
  • 讽刺的是(如果我没记错的话),Bjarne 的 C++ 前身,C with Classes,本质上是 C 上的一个宏层。
  • 最初的 1980 年代 C++ 实现基于一个特殊的 AT&T 预处理器,称为 cfront。后端是 C 编译器 cc。同样,StepStone, Inc(Brad Cox 的公司)在 1980 年代最初的 Objective-C 实现是 cc 前面的预处理器。在这两种情况下,首先运行 cpp,然后是 cfront 或 Objective C 预处理器,然后是标准 cc 工具链。这使得这两种语言都可以轻松移植到许多平台。
  • C# 确实有一个预处理器:en.csharp-online.net/CSharp_Preprocessor_Directives
【解决方案3】:

每种语言都需要一种单独编译的机制。理想情况下,语言将接口与实现区分开来,一个模块只依赖于它导出的模块的接口。 (参见,例如,Ada、Clu、Modula 等。)

C 没有用于接口或实现的语言结构。因为不同的 .c 文件共享接口的单一视图至关重要,所以编程学科演变为将声明(即接口)放在 .h 文件中并使用文本包含 (#include) 共享这些声明/接口。原则上,#define 和 #ifdef 可以省略,但 #include 不能。

如今的语言设计人员认识到,文本包含无法运行铁路,因此语言倾向于运行在单独编译的接口(Ada、Modula、OCaml)、编译器生成的接口(Haskell)或动态系统上保证接口一致性(Java、Smalltalk)。有了这样的机制,就不需要预处理器了,而且有很多理由不需要预处理器(想想源代码分析和调试)。

【讨论】:

  • +1 您的回答似乎是这里最中立和信息量最大的一个。我为你鼓掌。
  • 我认为#if 和朋友们仍然需要 C/C++ 中特定于平台的代码。
【解决方案4】:

因为这些语言的设计和目的不一样。

C 在构建时将预处理器视为一种强大的工具,它用于实现非常基本的东西(例如包含保护),开发人员可以使用它通过宏优化代码或选择性地包含/排除除了其他东西之外的某些代码块。 C++ 继承了 C 的大部分习语,不再使用宏来提高速度(因为引入了内联)但它仍然用于很多事情,请参阅帖子What are preprocessor macros good for?

【讨论】:

    【解决方案5】:

    因为 Gosling 和 Heilsberg 都了解滥用预处理所带来的风险和技术债务!

    【讨论】:

      【解决方案6】:

      预处理在 Java 世界中非常非常普遍。它用于弥补语言缺乏足够的内置抽象设施,否则会导致无休止的复制和粘贴样板代码。

      很多人没有意识到这是真的原因是,在 Java 世界中它被称为“代码生成”而不是“预处理”,因为“预处理器”听起来像讨厌的旧 C,而“代码生成”听起来像高效的成熟企业流程的专业工具。不过,它仍然是预处理,即使您必须花大价钱购买一个不兼容的非标准专有工具来完成它,而不仅仅是使用语言内置的工具。

      【讨论】:

      • 多么棒的答案,我真希望我能给它不止一个投票!
      • 是的,有时我在使用 Java 时真的很怀念预处理器
      【解决方案7】:

      我不同意在现代语言中不需要 cpp 的共识。我有很多情况,我有同一个程序的 3 个稍微不同的版本,我希望能够为每个版本进行大量更改。使用 CPP,我可以将它们全部放在 #if #else 块中,并且可以在编译行定义 #if。在 Java 中,我需要创建某种静态全局变量并在编译时对其进行初始化。我从来没有让它正常工作。

      【讨论】:

      • @Brian:哇!我并不惊讶它没有正常工作! Java 中更好的解决方案是使用子类或“策略”模式来区分版本之间的差异。
      • 但是如果差异是 10 行散布在一个类中,或者更糟的是多个类?这是一个很大的努力。
      • @Brian:你赢不了这场争论。这是 Java,所以更多的类 => 更好 ;-)
      【解决方案8】:

      C和C++中的预处理器有两个不同的功能

      • 在构建过程中将文件拉到一起 - 语言 像 Java 等。有自己的机制,比如 import 来做到这一点

      • 执行文本替换 - 这在 C 中在一定程度上仍然需要,但 C++ 可以(在大多数情况下)使用模板更好地做到这一点

      因此,C 和 C++ 都需要它作为第一个,但 C++ 可以将它用于第二个,尽管它甚至在 C++ 中也很有用 - 请参阅今天早些时候的 this question。

      【讨论】:

      • 某些事情,比如在日志或测试套件中使用__LINE__ 和__FILE__,如果没有预处理器就无法轻松完成(当我不得不用另一种语言来做这些事情时,我很难过没有预处理器)。
      【解决方案9】:

      现代语言在语言本身中包含预处理器!对于 C++,预处理器仅用于模块管理和条件包含等,非常有用。

      我相信它是一个单独的工具,因为编译器并不是我们今天所知的单一工具。我听说非常古老的 C 编译器用于生成文件的标记,然后在不同的阶段进行其余的编译。我能想到的主要原因是内存和其他资源与我们今天相比非常稀缺。

      【讨论】:

        【解决方案10】:

        您可以确定现代语言是用 C 或 C++ 编写的,并且在该实现本身中有宏。您需要它们来处理操作系统差异的一件事。动态/高级语言包装并隐藏了底部某处需要宏的许多东西。

        此外,宏有时也用于提高速度。在动态语言中,速度并不那么重要。

        【讨论】:

          【解决方案11】:

          您应该更仔细地研究一下 Perl。 Perl 支持source filters,它们基本上是用 Perl 编写的自定义 Perl 预处理器 :)

          【讨论】:

            【解决方案12】:

            Java 旨在避免一些使 C++ 难以使用的特性。

            C# 从 Java 复制(或继承)大部分设计决策。

            高级编程语言避免了这种低级工件。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-12-04
              • 2013-06-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多