为什么其他语言不支持类似于C及其后代的预处理程序指令?

时间:2010-07-10 05:06:15

标签: c programming-languages preprocessor


我想知道为什么其他语言不支持此功能。我可以理解C / C ++代码是依赖于平台的,因此可以通过使用预处理器指令实现跨各种平台的工作(编译和执行)。除此之外,还有许多其他用途。就像你可以将所有调试printf放在#if DEBUG ... #endif中。因此,在发布版本时,这些代码行不会被编译成二进制文件。
但在其他语言中,实现这个(后面的部分)很难(或者可能是不可能的,我不确定)。所有代码都将在二进制文件中编译,增加其大小。所以我的问题是“为什么Java, or other modern compiled languages不支持这种功能?”它允许您以非常方便的方式在二进制文件中包含或排除某些代码。

11 个答案:

答案 0 :(得分:9)

没有预处理器的主要语言通常采用不同的,通常更清晰的方式来实现相同的效果。

拥有像cpp这样的文本预处理器是一种喜忧参半的祝福。由于cpp实际上并不知道C ,所以它只是将文本转换为其他文本。这会导致许多维护问题。以C ++为例,其中明确弃用了预处理器的许多用途,以支持更好的功能,如:

  • 对于常数,const而不是#define
  • 对于小型功能,inline代替#define

C ++常见问题calls macros evil并提供了避免使用它们的多种理由。

答案 1 :(得分:7)

预处理器的可移植性优势远远超过滥用的可能性。以下是我在行业中看到的实际代码的一些示例:

  • 函数体与#ifdef纠结在一起,很难读取函数并弄清楚发生了什么。请记住,预处理器使用 text 而非语法,因此您可以执行非常不合理的事情

  • 代码可能会在#ifdef的不同分支中重复出现,因此很难保持关于正在发生的事情的单一事实。

  • 当应用程序用于多个平台时,编译所有代码变得非常困难,而不是为开发人员平台选择的代码。您可能需要设置多台计算机。 (例如,在BSD系统上设置一个准确模拟GNU标头的交叉编译环境是很昂贵的。)在大多数Unix专有且供应商必须全部支持它们的时代,这个问题非常严重。今天,当许多版本的Unix都是免费的时候,它就不那么大了,尽管在Unix环境中复制本机Windows头文件仍然非常具有挑战性。

  • 有些代码受到许多#ifdef的保护,您无法弄清楚选择代码需要-D选项的组合。问题是NP难,所以最着名的解决方案需要尝试指数多种不同的定义组合。这当然是不切实际的,所以真正的结果是逐渐使您的系统填充未编译的代码。这个问题会导致重构,当然这样的代码完全不受你的单元测试和回归测试的影响 - 除非你建立一个巨大的多平台测试服务器场,甚至可能不是那样。

    在现场,我已经看到这个问题导致重构应用程序经过仔细测试和发货的情况,只是为了接收应用程序甚至不会在其他平台上编译的即时错误报告。如果代码被#ifdef隐藏而我们无法选择它,我们无法保证它可以进行类型检查 - 甚至它在语法上是正确的。

硬币的另一面是更高级的语言和编程技术减少了预处理器中对条件编译的需求

  • 对于某些语言,如Java, all ,依赖于平台的代码位于JVM的实现中以及相关的库中。人们已经竭尽全力制作与平台无关的JVM和库。

  • 在许多语言中,例如Haskell,Lua,Python,Ruby等等,设计人员在与C相比减少平台相关代码的数量方面遇到了一些麻烦。

  • 在现代语言中,您可以将依赖于平台的代码放在编译接口后面的单独编译单元中。许多现代编译器都具有跨接口边界内联函数的良好工具,因此您不需要为这种抽象支付太多(或任何)惩罚。这不是C的情况,因为(a)没有单独编译的接口;单独编译模型假设#include和预处理器; (b)C编译器在具有64K代码空间和64K数据空间的机器上成熟;一个足够复杂的编译器可以跨模块边界内联,这几乎是不可想象的。今天这样的编译器是常规的。一些高级编译器内联和专门化方法动态

摘要:通过使用语言机制而不是文本替换来隔离依赖于平台的代码,将所有代码公开给编译器,所有内容都经过类型检查至少,你有机会做静态分析,以确保适当的测试覆盖率。您还排除了一大堆编码实践,导致代码无法读取。

答案 2 :(得分:6)

因为现代编译器在任何情况下都足够聪明地删除死代码,所以不再需要以这种方式手动输入编译器。即而不是:

#include <iostream>

#define DEBUG

int main()
{
#ifdef DEBUG
        std::cout << "Debugging...";
#else
        std::cout << "Not debugging.";
#endif
}

你可以这样做:

#include <iostream>

const bool debugging = true;

int main()
{
    if (debugging)
    {
        std::cout << "Debugging...";
    }
    else
    {
        std::cout << "Not debugging.";
    }
}

你可能会获得相同或至少相似的代码输出。


编辑/注意:在C和C ++中,我绝对不会这样做 - 我会使用预处理器,如果没有别的东西可以让我的代码的读者立即清楚它的一大块是不是'应该在某些条件下遵守。然而,我说这就是许多语言避开预处理器的原因。

答案 3 :(得分:2)

其他语言通过使用通用预处理器(如m4。)来支持此功能。

我们真的希望每种语言都有自己的文本替换执行前实现吗?

答案 4 :(得分:2)

请注意,宏或预处理/条件/等通常被认为是编译器/解释器功能,而不是语言功能,因为它们通常完全独立于正式语言定义,并且可能因编译器与编译器实现而异。同一种语言。

许多语言中的情况,其中条件编译指令可能比if-then-else运行时代码更好,因为编译时语句(例如变量声明)需要是有条件的。例如

$if debug
array x
$endif
...
$if debug
dump x
$endif

仅在需要x时声明/分配/编译x,而

array x
boolean debug
...
if debug then dump x

可能必须声明x,无论debug是否为真。

答案 5 :(得分:2)

C预处理器可以在任何文本文件上运行,不一定是C。

当然,如果使用其他语言运行,它可能会以奇怪的方式进行标记,但对于像#ifdef DEBUG这样的简单块结构,您可以将其置于任何语言中,在其上运行C预处理器,然后运行您的语言特定的编译器,它将工作。

答案 6 :(得分:1)

许多现代语言实际上具有超出CPP的语法元编程功能。几乎所有现代Lisps(Arc,Clojure,Common Lisp,Scheme,newLISP,Qi,PLOT,MISC ......)都有非常强大的(图灵完整的,实际上)宏系统,所以为什么他们应该把自己局限于那些甚至不是真正的宏的蹩脚的CPP风格的宏,只是文本片段?

具有强大语法元编程功能的其他语言包括Io,Ioke,Perl 6,OMeta,Converge。

答案 7 :(得分:1)

更好的问题是,为什么C使用预处理器来实现这些类型的元编程任务?它不是对当时技术进行妥协的特征。

C中的预处理器指令是在机器资源(CPU速度,RAM)稀缺(且价格昂贵)时开发的。预处理器提供了一种在内存有限的慢速机器上实现这些功能的方法。例如,我拥有的第一台机器有56KB的RAM和2Mhz的CPU。它仍然有一个完整的K&amp; R C编译器,可以将系统的资源推到极限,但是可以使用。

更多现代语言利用当今功能更强大的机器来提供更好的方法来处理预处理器用来处理的各种元编程任务。

答案 8 :(得分:0)

其他语言也有更好的动态绑定。例如,出于导出原因,我们有一些代码无法发送给某些客户。我们的“C”库使用#ifdef语句和精心制作的Makefile技巧(几乎相同)。

Java代码使用插件(ala Eclipse),因此我们只是不提供该代码。

你可以通过使用共享库在C中做同样的事情......但预处理器要简单得多。

答案 9 :(得分:0)

因为减小了二进制文件的大小:

  1. 可以通过其他方式完成(例如,将C ++可执行文件的平均大小与C#可执行文件进行比较)。
  2. 当你衡量它能够编写真正有效的程序时,它并不重要。

答案 10 :(得分:0)

没有人提到的另一点是平台支持。

大多数现代语言不能在与C或C ++相同的平台上运行,也不能在该平台上运行。例如,Java,Python和C#之类的本地编译语言都需要堆,它们被设计为在具有内存管理,库和大量空间的OS上运行,它们不能在独立的环境中运行。在那里,您可以使用其他方式将其存档。 C可以用于通过2KiB ROM对控制器进行编程,对于大多数应用程序,您需要一个预处理器。