C风格:宏还是预处理器?

时间:2009-03-28 07:24:41

标签: c macros preprocessor c-preprocessor

我已经编写了一个库来匹配一组模式的字符串,现在我可以轻松地将词汇扫描程序嵌入到C程序中。

我知道有许多成熟的工具可用于创建词法扫描程序(lex和re2c,仅列出前两个想到的名字)这个问题不是关于词法分析器,而是关于“扩展”的最佳方法“C语法。词法分析器示例只是一个普遍问题的具体案例。

我可以看到两种可能的解决方案:

  1. 编写一个预处理器,它将带有嵌入词法分析器的源文件转换为普通的C文件,并可能转换为要在编译中使用的一组其他文件。
  2. 编写一组C宏,以更易读的形式表示词法分析器。
  3. 我已经做过两个但问题是:“根据以下标准,您认为哪一种更好的做法?”

    • 可读性。词法分析器逻辑应清晰易懂
    • 可维护性。找到并修复错误不应该是一场噩梦!
    • 构建过程中的干扰。预处理器在构建过程中需要一个额外的步骤,预处理器必须在路径等等。

    换句话说,如果你不得不维护或编写一个使用这两种方法之一的软件,哪一种会让你失望?

    例如,以下是针对以下问题的词法分析器:

    • 求和所有数字(可以是十进制形式,包括指数,如1.3E-4.2)
    • 略读字符串(双引号和单引号)
    • 跳过列表(类似于LISP列表:(3 4(0 1)()3))
    • 在遇到单词end(case无关紧要)或缓冲区末尾时停止

    两种风格。

    /**** SCANNER STYLE 1 (preprocessor) ****/
    #include "pmx.h"
    
    t = buffer
    
    while (*t) {
      switch pmx(t) { /* the preprocessor will handle this */
        case "&q" :         /* skip strings */
          break; 
    
        case "&f<?=eE>&F" : /* sum numbers */ 
          sum += atof(pmx(Start,0));
          break;
    
        case "&b()":        /* skip lists */
          break;
    
        case "&iend" :      /* stop processing */ 
          t = "";
          break;
    
        case "<.>":         /* skip a char and proceed */
          break;
      }
    }
    

    /**** SCANNER STYLE 2 (macros) ****/
    #include "pmx.h"
    /* There can be up to 128 tokens per scanner with id x80 to xFF */
    #define TOK_STRING x81
    #define TOK_NUMBER x82
    #define TOK_LIST   x83
    #define TOK_END    x84
    #define TOK_CHAR   x85
    
    pmxScanner(   /* pmxScanner() is a pretty complex macro */
       buffer
     ,
       pmxTokSet("&q"         , TOK_STRING)
       pmxTokSet("&f<?=eE>&F" , TOK_NUMBER)
       pmxTokSet("&b()"       , TOK_LIST)
       pmxTokSet("&iend"      , TOK_END)
       pmxTokSet("<.>"        , TOK_CHAR)
     ,
       pmxTokCase(TOK_STRING) :   /* skip strings */
         continue; 
    
       pmxTokCase(TOK_NUMBER) :   /* sum numbers */ 
         sum += atof(pmxTokStart(0));
         continue;
    
       pmxTokCase(TOK_LIST):      /* skip lists */
         continue;
    
       pmxTokCase(TOK_END) :      /* stop processing */ 
         break; 
    
       pmxTokCase(TOK_CHAR) :     /* skip a char and proceed */
         continue;
    );
    

    如果有人对当前的实现感兴趣,代码就在这里:http://sites.google.com/site/clibutl

2 个答案:

答案 0 :(得分:6)

预处理器将提供更强大和通用的解决方案。另一方面,宏可以快速启动,提供良好的概念验证,并且当示例关键字/令牌空间很小时很容易。扩展/包含新功能可能会在一点之后使用宏变得乏味。我会说启动宏然后将它们转换为预处理程序命令。

此外,尽可能尝试使用通用预处理器而不是自己编写。

  

[...]我将有另外一个依赖项来处理(例如,Windows的m4)。

是。但是你写的任何解决方案也是如此:) - 必须维护它。您命名的大多数程序都有可用的Windows端口(例如,请参阅m4 for windows)。使用这种解决方案的好处是可以节省批次的时间。当然,缺点是您可能必须加快源代码的速度,如果奇怪的错误出现(尽管维护这些错误的人非常有帮助,肯定会确保您得到所有​​帮助)。

同样,是的,我更喜欢打包自己的打包解决方案。

答案 1 :(得分:3)

自定义预处理器是解析器/解释器生成器中的典型方法,因为宏的可能性非常有限,并且在扩展阶段提供潜在的问题,使调试工作变得非常大。

我建议您使用经过时间考验的工具,例如经典的Yacc / Lex Unix程序,或者如果您想“扩展”C,请使用C ++和Boost :: spirit,这是一个广泛使用模板的解析器生成器。 / p>