现代语言中的预处理器已经过时了吗?
我正在为我正在创建的简单宠物语言制作一个简单的编译器,并且来自 C 背景(尽管我是用 Ruby 编写的)我想知道是否需要预处理器。
你怎么认为?现代语言中还需要“哑”预处理器吗? C# 的条件编译功能会被视为“预处理器”吗?是否每种不包含预处理器的现代语言都具有正确替换它所需的实用程序? (例如,由于模板的原因,C++ 预处理器现在大部分已经过时(尽管仍然依赖)。)
I'm making a simple compiler for a simple pet language I'm creating and coming from a C background(though I'm writing it in Ruby) I wondered if a preprocessor is necessary.
What do you think? Is a "dumb" preprocessor still necessary in modern languages? Would C#'s conditional compilation capabilities be considered a "preprocessor"? Does every modern language that doesn't include a preprocessor have the utilities necessary to properly replace it? (for instance, the C++ preprocessor is now mostly obsolete(though still depended upon) because of templates.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
C 的预处理可以做非常巧妙的事情,但是如果您查看它的用途,您就会意识到它通常只是添加另一个抽象级别。
所以我的答案是:如果你的语言足够高级,你就不需要预处理器 *。我不会说预处理是邪恶的或无用的,我只是说语言越抽象,我认为它需要预处理的理由就越少。
* 什么是足够高级?当然,这完全是主观的。
编辑:当然,我实际上只是指宏。使用预处理器与其他代码文件交互或定义常量是邪恶的。
C's preprocessing can do really neat things, but if you look at the things it's used for you realize it's often for just adding another level of abstraction.
So my answer is: you don't need a preprocessor if your language is high-level enough *. I wouldn't call preprocessing evil or useless, I just say that the more abstract the language gets, the less reason I can think for it needing preprocessing.
* What's high-level enough? That is, of course, entirely subjective.
EDIT: Of course, I'm only really referring to macros. Using preprocessors for interfacing with other code files or for defining constants is evil.
预处理器是一种以丑陋的方式向语言提供不完整的元编程设施的廉价方法。
相反,更喜欢真正的元编程或 Lisp 风格的宏。
The preprocessor is a cheap method to provide incomplete metaprogramming facilities to a language in an ugly fashion.
Prefer true metaprogramming or Lisp-style macros instead.
预处理器不需要。对于真正的元编程,您应该拥有诸如 MetaML 或 Template Haskell 之类的东西,或者像 Scheme 那样的卫生宏。对于快速而肮脏的东西,如果您的用户绝对必须拥有它,那么
m4
总是有的。但是,现代语言应该支持与 C 的
#line
指令等效的功能。此类指令使编译器能够定位原始源代码中的错误,即使该源代码嵌入在解析器生成器或词法分析器生成器或文字程序。换句话说,A preprocesssor is not necessary. For real metaprogramming, you should have something like MetaML or Template Haskell or hygienic macros à la Scheme. For quick and dirty stuff, if your users absolutely must have it, there's always
m4
.However, a modern language should support the equivalent of C's
#line
directives. Such directives enable the compiler to locate errors in the original source, even when that source is embedded in a parser generator or a lexer generator or a literate program. In other words,我认为预处理器是让表达能力较差的语言继续行走的拐杖。
我见过太多对预处理器的滥用,以至于我非常讨厌它们。
I think that preprocessors are a crutch to keep a language with poor expressive power walking.
I have seen so much abuse of preprocessors that I hate them with a passion.
预处理器是编译的一个单独阶段。
虽然预处理在某些情况下很有用,但它可能导致的麻烦和错误使其成为一个问题。
在 C 中,预处理器主要用于:
所以我的答案是,预处理器虽然功能强大,但会损害可读性和/或不是处理某些问题的最佳方法。较新的语言往往认为代码维护非常重要,因此预处理器似乎已经过时了。
A preprocessor is a separated phase of compilation.
While preprocessing can be useful in some cases, the headaches and bugs it can cause make it a problem.
In C, preprocessor is used mostly for:
So my answer is while powerful, the preprocessor harms readability and/or isn't the best way to deal with some problems. Newer languages tend to consider code maintenance very important, and for those reasons the preprocessor appears to be obsolete.
这是您的语言,因此您可以在语言本身中构建您想要的任何功能,而无需预处理器。我认为预处理器不是必需的,它在语言之上增加了一层复杂性和模糊性。大多数现代语言没有预处理器,而在 C++ 中,只有在别无选择时才使用它。
顺便说一句,我相信 D 可以在没有预处理器的情况下处理条件编译。
It's your language so you can build whatever capabilities you want into the language itself, without a need for a preprocessor. I don't think a preprocessor should be necessary, and it adds a layer of complexity and obscurity on top of a language. Most modern languages don't have preprocessors, and in C++ you only use it when you have no other choice.
By the way, I believe D handles conditional compilation without a preprocessor.
这完全取决于您提供的其他功能。例如,如果我有一个 const int N,你们是否愿意让我采用 N 个变量?有 N 个成员变量,用一个参数来构造所有它们?创建N个函数?执行 N 个不一定在循环中工作的操作(例如,传递 N 个参数)? N 个模板参数?条件编译?常数不是整数吗?
C 预处理器在适当的人手中是如此强大,以至于您需要创建一种非常强大的语言来保证它不被使用。
It depends on exactly what other features you offer. For example, if I have a const int N, do you offer for me to take N variables? Have N member variables, take an argument to construct all of them? Create N functions? Perform N operations that don't necessarily work in loops (for example, pass N arguments)? N template arguments? Conditional compilation? Constants that aren't integral?
The C preprocessor is so absurdly powerful in the proper hands, you'd need to make a seriously powerful language not to warrant one.
我想说的是,尽管您应该避免使用预处理器来完成您通常所做的大多数事情,但它仍然是必要的。
例如,在 C++ 中,为了编写像 Catch 这样的单元测试库,预处理器是绝对有必要。他们以两种不同的方式使用它:一种用于断言扩展1,另一种用于测试用例中的嵌套部分2。
但是,预处理器不应该被滥用来在可以使用 const 表达式和模板元编程的 C++ 中进行编译时计算。
抱歉,我没有足够的声誉来发布两个以上的链接,所以我将其放在这里:
I would say that although you should avoid the pre-processor for most everything you normally do, it's still necessary.
For example, in C++, in order to write a unit-testing library like Catch, a pre-processor is absolutely necessary. They use it in two different ways: One for assertion expansion1, and one for nesting sections in test cases2.
But, the pre-processor shouldn't be abused to do compile-time computations in C++ where const-expressions and template meta-programming can be used.
Sorry, I don't have enough reputation to post more than two links, so I'm putting this here:
其他人指出,C 预处理器提供的许多功能是为了弥补 C 语言的局限性。例如,#include 和包含防护的存在是由于缺少 import 语句,而宏主要是由于缺少内联函数和常量声明而存在。
然而,C 预处理器的一个功能在更现代的语言中仍然有用,那就是 #line 指令,因为它支持使用语义丰富的预处理器/编译器。举个例子,考虑 yacc,它是一种领域特定语言 (DSL),用于将解析器编写为 BNF 语法规则的集合。
yacc
的一个核心功能是称为“动作”的 C 代码块可以嵌入到 BNF 规则中。当 BNF 规则用于解析输入文件的一部分时,将执行嵌入该规则的操作。 yacc 编译器生成一个 C 文件,该文件实现输入文件中指定的基于 BNF 的解析器,输入 Yacc 文件中出现的任何操作都会复制到生成的 C 文件中,但每个操作都被包围通过#line
指令。使用#line
指令有两个重要的好处。首先,如果操作中存在语法错误,则 C 编译器生成的错误消息可以指定错误发生在
, line 42
而不是.c 第 3967 行
。其次,#line 指令提供的位置信息被复制到 C 编译器创建的生成目标代码文件中。因此,如果您使用调试器来调查程序崩溃,如果导致崩溃的错误源自嵌入在 Yacc 输入文件中的操作,那么调试器将报告源代码中该错误行的位置
,第 42 行
,而不是.c,第 3967 行
。C# 和 Perl 的设计者明智地提供了
#line
指令。不幸的是,许多其他语言(首先想到的是 Java)的设计者忽略了提供#line
指令。因此,许多语言的类似 Yacc 的解析器生成器无法将嵌入操作的源位置传达给编译器(因此也无法传达给调试器)。A others have pointed out, much of the functionality provided by the C preprocessor exists to compensate for limitations of the C language. For example,
#include
and inclusion guards exist due to the lack of animport
statement, and macros largely exist due to the lack of inline functions and constant declarations.However, the one feature of the C preprocessor that would still be beneficial in more modern languages is the
#line
directive, since this supports the use of semantically-rich preprocessors/compilers. An an example, consideryacc
, which is a domain-specific-language (DSL) for writing a parser as a collection of BNF grammar rules. A central feature ofyacc
is that chunks of C code called actions can be embedded within BNF rules. When a BNF rule is used to parse a piece of an input file, an action embedded in that rule will be executed. Theyacc
compiler generates a C file that implements the BNF-based parser specified in the input file, and any actions that appeared in the input Yacc file are copied to the generated C file, but each action is surrounded by#line
directives. This use of#line
directives provides two important benefits.First, if there is a syntax error in an action, then the error message generated by the C compiler can specify that the error occurred in, say,
<input-file-to-yacc>, line 42
rather than in<output-file-generated-by-yacc>.c, line 3967
.Second, the location information provided by
#line
directives is copied into generated object code files created by the C compiler. So if you are using a debugger to investigate a program crash, if the bug that caused the crash originated from an action embedded in a Yacc input file, then the debugger will report the location of that buggy line of source code as being in<input-file-to-yacc>, line 42
rather than in<output-file-generated-by-yacc>.c, line 3967
.The designers of C# and Perl wisely provided a
#line
directive. Unfortunately, the designers of many other languages (Java being one that springs to mind) neglected to provide a#line
directive. Because of this, Yacc-like parser generators for many languages are unable to communicate the source location of embedded actions to compilers (and, therefore, to debuggers).