[开发过程]宏函数及调试

    在使用了大量的宏来快速构建函数后,一个问题出现了:如果你发现某个地方出了bug,你得定位它,于是使用debugger,然而宏的“简易”导致你进行了许多次“下一步”还是停留在使用宏的那一行。

    当然也不是完全没办法,如果你曾经接触过Python语言,并且听说过“Python语言八荣八耻”,那么你应该对这句话有印象——以打印日志为荣,以单步跟踪为耻。虽然我不太明白为什么Python语言有这样的论调,不过至少在使用宏的时候,这一点是非常值得借鉴的。废话少说,先来个实现:

#define shift(current, encounter, target)                                 \
    static void linkName(current, encounter)(struct LRAnalyser* self,     \
                                             struct Token* t)             \
    {                                                                     \
        self->stateStack->push(self->stateStack, jerryLRStates + target); \
        printf(#current                                                   \
               " encouters "                                              \
               #encounter                                                 \
               " shift to "                                               \
               #target                                                    \
               "\n");                                                     \
    } /*******************************************************************/

这里展示的是在原来的宏shift内部增加一个输出语句,输出当前状态名、遭遇的符号类型、目标状态名。有了这些信息,LR分析器每一次移进你都不会错过——记得在那几个特殊的移进函数中也插入类似代码。如果有需要,也可以插入其他输出函数,把它们都塞进宏内部。不过注意版式,或者输出到特定的文件以保护视力。

 

    说到跟踪调试和打印日志,围绕这个话题有很多争论。单步跟踪直观,而且不会破坏程序的执行,但是如果有一个很长的循环,比如循环1000次(这还不算很长吧),其中某些情况下会有故障发生,那么跟踪这个循环会让人厌倦于“下一步”和观察变量值;另一方面,一个printf更适合对付那些循环,但是在递归迭代过程中它就显得力不从心了——或者应该说观察输出的程序员往往很难确定到底哪个输出对应于哪一次递归调用——此外安插在代码中间的这些探针在不再需要时,清除它们也是一件很麻烦的事情,也许一不小心把需要的代码也给顺手牵羊了。两种方法应该互补,在恰当的情况下使用;或者,使用你认为可以解决问题的方法。

 

    最后再说说宏。在C语言程序开发过程中,宏大部分情况下是用来伪装一个函数或常数,如EOF以及可变参数函数中用到的va_arg宏,此外还有一些经典的宏用来包装一个函数(可以认为这是Decorator模式的始祖),如assert宏。不过,像LR分析表实现过程中这样用来大段大段生成函数的宏并不多见。可以认为这是一种C语言风格的原始泛型编程,甚至于是一种编译时多态:每个函数都是一个对象,它们之间的差异仅体现在函数名和target成员,而每次调用宏时就如同调用构造函数一样。不过,它们的缺陷是,它们总会在内存中驻留,占用很多空间。

你可能感兴趣的:(编程,C++,c,python,C#)