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

时间:2010-05-06 21:44:04

标签: c++ c c-preprocessor language-design

在他的FAQ中,Bjarne Stroustrup说:

  

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

当我读到这篇文章时,它引起了我对 C 预处理器的兴趣。我已经看到它的宏功能适合于简化常用表达式,但没有想到它能够在我想要将类带到C的级别上显着添加语法和语义。

所以现在我有些疑问:

  1. 这种方法的其他例子是从 C 引导语言吗?

  2. Stroustrup原创作品的来源是否可在任何地方使用?

  3. 我在哪里可以了解有关利用此技术的具体细节?

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

7 个答案:

答案 0 :(得分:12)

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

答案 1 :(得分:5)

有关您可以使用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

答案 2 :(得分:4)

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

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

像Common Lisp这样的语言有足够的宏功能来添加新的语法和语义,还有像Forth这样的语言,系统足够灵活,可以适应变化,但这对大多数语言都不适用。

答案 3 :(得分:3)

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

那就是说,你可以用CPP和递归做疯狂的事情;我很漂亮 确定它的图灵完成。我即将链接的图书馆使用很多 获得有趣行为的丑陋技巧。虽然你可以建立一种 在最优雅,许多人可能会认为它是Turing Tarpit

有关此内容的 gentler 简介,请尝试Cloak

要深入了解,请看

  • Boost - "跨平台",但更丑陋;流行的C ++库的一部分
  • Chaos - 由Boost-pp人员跟进,但仅支持符合C99标准的工具,而且更优雅。
  • Order - 据我所知,一种类似Lisp的语言,受混沌的启发,建立在纯粹的CPP上

E.g。使用Order或Chaos,您可以在纯CPP中编写递归的fibonacci序列生成器。

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

C预处理器非常有限。它可以用来为常用表达式制作缩写,但就是这样。当您尝试使用它来定义新的语言结构时,它会变得更加麻烦和脆弱。

答案 6 :(得分:0)

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

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

一般情况下,使用C预处理器来开发某种混淆语言,例如Steve Bourne在编写Bourne Shell for Unix时所做的一项活动,我认为这种活动适合引渡,然后是多次水上寄宿会议。

要记住关于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 topic Macro Pitfalls

您可以使用C预处理器作为一般宏和文本预处理器,它针对C编译器以外的某些工具。例如,较旧的imake utility for build automation使用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)) },