作为语言创建工具的 C 预处理器的长度/限制是多少?我可以在哪里了解更多有关这些的信息?

发布于 2024-09-01 08:11:23 字数 687 浏览 2 评论 0原文

Bjarne Stroustrup 在他的常见问题解答中说道:

构建[Cfront,第一个 C++ 编译器],我首先用C写了一个 “C with Classes”-to-C 预处理器。 “C with Classes”是一种 C 语言, 成为C++的直接祖先... 然后我写了第一个版本 “C with Classes”中的 Cfront。

当我读到这篇文章时,它激起了我对 C 预处理器的兴趣。我认为它的宏功能适合简化通用表达式,但没有想到它能够显着增加语法和语义,达到我想象的将类引入 C 所达到的水平。

所以现在我脑子里有一些问题:

  1. 这种从C引导语言的方法还有其他示例吗?

  2. Stroustrup 原创作品的来源可以在任何地方找到吗?

  3. 我可以在哪里了解有关使用此技术的更多细节?

  4. 该方法的长度/限制是什么?比如说,可以创建一组预处理器宏,让某人可以编写一些与 Lisp/Scheme 类似的东西吗?

In his FAQ, Bjarne Stroustrup says:

To build [Cfront, the first C++
compiler], I first used C to write a
"C with Classes"-to-C preprocessor. "C
with Classes" was a C dialect that
became the immediate ancestor to C++...
I then wrote the first version of
Cfront in "C with Classes".

When I read this, it piqued my interest in the C preprocessor. I'd seen its macro capabilities as suitable for simplifying common expressions but hadn't thought about its ability to significantly add to syntax and semantics on the level that I imagine bringing classes to C took.

So now I have some questions on my mind:

  1. Are there other examples of this approach to bootstrapping a language off of C?

  2. Is the source to Stroustrup's original work available anywhere?

  3. Where could I learn more about the specifics of utilizing this technique?

  4. What are the lengths/limits of that approach? Could one, say, create a set of preprocessor macros that let someone write in something significantly Lisp/Scheme like?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

绅士风度i 2024-09-08 08:11:23

请注意,Stroustrup并没有说他使用 C 预处理器 (cpp) 来创建 C With Classes - 他没有。他用 C 编写了自己的预处理器。Cfront 是一个真正的编译器,而不是预处理器。事实上,C 预处理器非常不适合语言开发,因为它没有任何解析能力。

Note that Stroustrup is not saying that he used the C preprocessor (cpp) to create C With Classes -he didn't. He wrote his own preprocessor using C. And Cfront was a true compiler not a preprocessor at all. The C preprocessor is in fact deeply unsuitable for language development, as it has no parsing abilities whatsoever.

情场扛把子 2024-09-08 08:11:23

有关可以使用 C 预处理器创建的“语言”的怪物示例,请查看以下头文件:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src /cmd/sh/mac.h

它来自 Steve Bourne 编写的原始 Unix shell 的源代码,旨在将 C 转换为类似 Algol 的语言。下面是一段代码在使用时的样子:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/args.c

那看起来有点奇怪,但它仍然是 C。它可能看起来像一种不同的语言,但因为它是在预处理器中实现的,所以没有语法支持,例如,

WHILE foo
DO
    SWITCH
    ....
    ENDSW
OD

一切都很好并且编译得很好,但也是如此

WHILE foo
DO
    SWITCH
    ....
    OD
ENDSW

For an example of the kind of monstrosity of a "language" you can create using the C preprocessor, have a look at this header file:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h

It's from the source code of the original Unix shell written by Steve Bourne and it aims to turn C into an Algol like language. Here is an example of what a piece of code looks like when using it:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/args.c

That looks kind of bizarre but it is still C. It may look like a different language, but because it is implemented in the preprocessor, there's no syntactic support for it e.g.

WHILE foo
DO
    SWITCH
    ....
    ENDSW
OD

is all very fine and compiles nicely, but so does

WHILE foo
DO
    SWITCH
    ....
    OD
ENDSW
说不完的你爱 2024-09-08 08:11:23

C 预处理器不是您想要的。它无法像 Cfront 那样添加语法和语义。

Cfront 是一个实际的编译器,它将 C with Classes(后来的 C++)翻译成 C。它只是一个预处理器,因为它在 C 编译器之前运行。我曾经使用过一个名为 f2c 的程序将 FORTRAN 77 代码转换为 C 代码。它的工作原理相同。

有些语言(例如 Common Lisp)具有足够的宏功能来添加新的语法和语义,而有些语言(例如 Forth)的系统足够灵活以适应更改,但这不适用于大多数语言。

The C preprocessor isn't what you're looking for. It can't add syntax and semantics like Cfront did.

Cfront was an actual compiler that translated C with Classes, later C++, to C. It was a preprocessor only in that it ran before the C compiler. I used a program called f2c once to translate FORTRAN 77 code to C code. It worked on the same principle.

There are languages like Common Lisp with enough macro power to add new syntax and semantics, and languages like Forth where the system is flexible enough to accomodate changes, but that won't work for most languages.

沉默的熊 2024-09-08 08:11:23

正如其他人提到的,C++ 不是使用 C 预处理器 (CPP) 创建的。

也就是说,您可以使用 CPP 和递归做疯狂的事情;我很漂亮
当然它是图灵完备的。我将要链接的库很多
丑陋的技巧来获得有趣的行为。虽然你可以构建一种
优雅至上,许多人可能会认为它是Turing Tarpit

对于这些东西的更温和介绍,请尝试Cloak

要深入了解,请查看

  • Boost -- “跨平台”,但更丑陋;流行的 C++ 库 Chaos 的一部分
  • - 由 Boost-pp 人员跟进,但仅支持 C99 兼容工具,因此更优雅
  • Order —— 据我所知,这是一种类似 Lisp 的语言,受到启发由 Chaos 开发,基于纯

CPP使用 Order 或 Chaos,您可以用纯 CPP 编写递归斐波那契序列生成器。

As mentioned by others, C++ was not created using the C preprocessor (CPP).

That said, you can do insane things with the CPP and recursion; I'm pretty
sure it's Turing Complete. The libraries I'm about to link to use a lot of
ugly tricks to get interesting behavior. Although you can build a kind of
elegance on top, many might consider it a Turing Tarpit.

For a gentler introduction to this stuff, try Cloak.

To go deeper, look at

  • Boost -- "cross platform", but uglier for it; part of popular C++ library
  • Chaos -- followup by Boost-pp guy, but supports only C99-compliant tools, ergo more elegant
  • Order -- from what I can tell, a Lisp-like language, inspired by Chaos, built on pure CPP

E.g. with Order or Chaos, you can write a recursive fibonacci sequence generator in pure CPP.

嘿嘿嘿 2024-09-08 08:11:23

我认为 Objective-C 也是以同样的方式开始的。它是一个预处理器,用于构建一些 C 代码,然后将其传递给 C 编译器。但它不是 #define FOO 意义上的 C 预处理器,它作为标准 C 预处理器之前或之后的附加步骤运行。然后可以将任意数量的预处理器步骤的结果发送到 C 编译器。

I think Objective-C started out the same way. It was a preprocessor that built some C code that was then passed to a C compiler. But it was not THE C preprocessor in the sense of #define FOO, it ran as an additional step before or after the standard C preprocessor. The result of any number of preprocessor steps can then be sent to the C compiler.

蒲公英的约定 2024-09-08 08:11:23

听起来他的“C with Classes”-to-C 预处理器与标准 C 预处理器不同,因为他特别提到自己编写了这个预处理器。

C 预处理器非常有限。它可以用来制作常见表达式的简写,但仅此而已。当您尝试用它定义新的语言结构时,它很快就会变得更加麻烦和脆弱。

It sounds like his "C with Classes"-to-C preprocessor was not the same thing as the standard C preprocessor, since he speaks specifically of writing this preprocessor himself.

The C preprocessor is very limited. It can be used to make shorthands for common expressions, but that's about it. When you attempt to define new language constructs with it, it rapidly becomes more cumbersome and brittle.

通知家属抬走 2024-09-08 08:11:23

我建议您从 GCC 宏文档 开始,它提供了很多有趣的信息关于 C 预处理器的 GCC 实现。

Clay Bridges 在他的回答中提供了几个使用 C 预处理器的示例。关于 Order 语言的内容很有趣,有很多例子。 Order 的作者确实提出了他/她遇到的一个问题,C 预处理器实现可能无法完全实现更新的标准。

一般来说,使用 C 预处理器开发某种混蛋语言,例如 Steve Bourne 在为 Unix 编写 Bourne Shell 时所做的事情,我认为这是一项适合进行表演的活动,然后进行多次水刑。

关于 C 预处理器,要记住的主要一点是它正在操作文本标记。因此,C 预处理器将允许对语法进行大量修改。例如,下面的宏使用 Visual Studio 2005 编译没有错误,显示了可能的不直观的文本操作。

#define TESTOP(a,x,y,op,z)  a (x) op (y); z

void f(void)
{
    int i = 0, j = 5;

    TESTOP( ,i,j,+=, );
    TESTOP( ,i,(j + 2),+=, );
    TESTOP({,i,(j + 2),+=,});
}

然而,在突破界限时,您确实需要了解并解决 C 预处理器的一些限制。请参阅 GCC 主题宏陷阱,了解一些需要考虑的问题。

您可以将 C 预处理器用作通用宏和文本预处理器,以 C 编译器以外的某些工具为目标。例如,旧的用于构建自动化的imake实用程序使用C预处理器来提供广泛的宏工具。

我发现 C 预处理器最有效的用途是简化复杂的代码和声明。

我见过的一个案例是使用 C 预处理器提供一种状态机语言,用于创建数据结构和数据来描述状态机。然后将生成的数据结构用作状态机函数的参数。这允许用 C 预处理器定义的语言编写多个不同的状态机过程,并由单个函数完成状态机处理。

Microsoft 在其 Microsoft 基础类 (MFC) 中使用 C 预处理器来隐藏 MFC 的大量消息传递细节。一旦你习惯了,下面的内容就很容易阅读。由于 Visual Studio IDE 具有使用宏生成和修改代码的工具,因此对于程序员来说非常简单。

BEGIN_MESSAGE_MAP(CFrameworkWndDoc, CWindowDocument)
    //{{AFX_MSG_MAP(CFrameworkWndDoc)
    ON_WM_CHAR()
    ON_WM_TIMER()
    ON_MESSAGE(WU_EVS_DFLT_LOAD, OnDefaultWinLoad)
    ON_MESSAGE(WU_EVS_POPUP_WINDOW, OnPopupWindowByName)
    ON_MESSAGE(WU_EVS_POPDOWN_WINDOW, OnPopdownWindowByName)
    ON_MESSAGE(WM_APP_CONNENGINE_MSG_RCVD, OnConnEngineMsgRcvd)
    ON_MESSAGE(WM_APP_XMLMSG_MSG_RCVD, OnXmlMsgRcvd)
    ON_MESSAGE(WM_APP_BIOMETRIC_MSG_RCVD, OnBiometricMsgRcvd)
    ON_MESSAGE(WM_APP_SHUTDOWN_MSG, OnShutdownMsgRcvd)
    ON_MESSAGE(WM_POWERBROADCAST, OnPowerMsgRcvd)
    ON_MESSAGE(WM_APP_SHOW_HIDE_GROUP, OnShowHideGroupMsgRcvd)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

特别是当您看到所使用的宏是什么样子时:

    #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
        PTM_WARNING_DISABLE \
        const AFX_MSGMAP* theClass::GetMessageMap() const \
            { return GetThisMessageMap(); } \
        const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
        { \
            typedef theClass ThisClass;                        \
            typedef baseClass TheBaseClass;                    \
            static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
            {

    #define END_MESSAGE_MAP() \
            {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
        }; \
            static const AFX_MSGMAP messageMap = \
            { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
            return &messageMap; \
        }                                 \
        PTM_WARNING_RESTORE

// for Windows messages
#define ON_MESSAGE(message, memberFxn) \
    { message, 0, 0, 0, AfxSig_lwl, \
        (AFX_PMSG)(AFX_PMSGW) \
        (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
        (memberFxn)) },

#define ON_WM_TIMER() \
    { WM_TIMER, 0, 0, 0, AfxSig_vw, \
        (AFX_PMSG)(AFX_PMSGW) \
        (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT_PTR) > ( &ThisClass :: OnTimer)) },

I suggest you start with the GCC Macros documentation which provides quite a bit of interesting information about the GCC implementation of the C Preprocessor.

Clay Bridges in his answer provides a couple of examples of using the C Preprocessor. The one about the Order language is interesting with a number of examples. The author of Order does bring up one issue that he/she ran into, C Preprocessor implementations may not fully implement more recent standards.

In general using the C Preprocessor to develop some kind of a bastardized language such as what Steve Bourne did when writing the Bourne Shell for Unix is an activity that I would consider suitable grounds for rendition followed by multiple water boarding sessions.

The main thing to remember about the C Preprocessor is that it is manipulating text tokens. So the C Preprocessor will allow quite a bit of tinkering with syntax. For instance the following macro, which compiles with Visual Studio 2005 without errors, shows the unintuitive text manipulation possible.

#define TESTOP(a,x,y,op,z)  a (x) op (y); z

void f(void)
{
    int i = 0, j = 5;

    TESTOP( ,i,j,+=, );
    TESTOP( ,i,(j + 2),+=, );
    TESTOP({,i,(j + 2),+=,});
}

However you do need to understand and work around some of the limitations of the C Preprocessor when pushing the boundaries. See the GCC topic Macro Pitfalls for some of the issues to consider.

And you can use the C Preprocessor as a general macro and text preprocessor that targets some tool other than the C compiler. For instance the older imake utility for build automation used the C Preprocessor to provide an extensive macro facility.

Where I have seen the C Preprocessor used most effectively was in simplifying complex code and declarations.

One case that I have seen was using the C Preprocessor to provide a state machine language which was used to create the data structures and data to describe a state machine. The resulting data structures were then used as an argument to a state machine function. This allowed multiple different state machine procedures to be written in the C Preprocessor defined language with the state machine processing done by a single function.

Microsoft, in their Microsoft Foundation Classes (MFC), used the C Preprocessor to hide quite a few of the messaging details of MFC. Once you get used to it something like the following is reasonably easy to read. Since the Visual Studio IDE had tools to generate and modify the code using the macros it was pretty straight forward for the programmer.

BEGIN_MESSAGE_MAP(CFrameworkWndDoc, CWindowDocument)
    //{{AFX_MSG_MAP(CFrameworkWndDoc)
    ON_WM_CHAR()
    ON_WM_TIMER()
    ON_MESSAGE(WU_EVS_DFLT_LOAD, OnDefaultWinLoad)
    ON_MESSAGE(WU_EVS_POPUP_WINDOW, OnPopupWindowByName)
    ON_MESSAGE(WU_EVS_POPDOWN_WINDOW, OnPopdownWindowByName)
    ON_MESSAGE(WM_APP_CONNENGINE_MSG_RCVD, OnConnEngineMsgRcvd)
    ON_MESSAGE(WM_APP_XMLMSG_MSG_RCVD, OnXmlMsgRcvd)
    ON_MESSAGE(WM_APP_BIOMETRIC_MSG_RCVD, OnBiometricMsgRcvd)
    ON_MESSAGE(WM_APP_SHUTDOWN_MSG, OnShutdownMsgRcvd)
    ON_MESSAGE(WM_POWERBROADCAST, OnPowerMsgRcvd)
    ON_MESSAGE(WM_APP_SHOW_HIDE_GROUP, OnShowHideGroupMsgRcvd)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Especially when you see what the macros used look like:

    #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
        PTM_WARNING_DISABLE \
        const AFX_MSGMAP* theClass::GetMessageMap() const \
            { return GetThisMessageMap(); } \
        const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
        { \
            typedef theClass ThisClass;                        \
            typedef baseClass TheBaseClass;                    \
            static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
            {

    #define END_MESSAGE_MAP() \
            {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
        }; \
            static const AFX_MSGMAP messageMap = \
            { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
            return &messageMap; \
        }                                 \
        PTM_WARNING_RESTORE

// for Windows messages
#define ON_MESSAGE(message, memberFxn) \
    { message, 0, 0, 0, AfxSig_lwl, \
        (AFX_PMSG)(AFX_PMSGW) \
        (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
        (memberFxn)) },

#define ON_WM_TIMER() \
    { WM_TIMER, 0, 0, 0, AfxSig_vw, \
        (AFX_PMSG)(AFX_PMSGW) \
        (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT_PTR) > ( &ThisClass :: OnTimer)) },
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文