C语言程序中为什么要使用debug宏

C语言程序中为什么要使用debug宏?

本文只是提供一种思路,一种个人经验,旨在用一种很粗俗也比较接地气的方式,提示菜鸟,为什么、以及怎样在C语言程序中引入debug宏,养成并保持良好习惯。


面试被问到调试的问题,

我说:初期printf,后来gdb,

又问:printf怎么搞?直接改程序?

回答:是~!

他没提,我也知道,应该用debug宏,但真的是没怎么用那东西,也没想解释太多,回想了一下不用debug宏的原因:

我一个小程序试很多东西,改一会,思路和程序结构和参数设置全变样了,为了便捷地测试一些想要的C语言特性或运行结果,不停的在源程序上加对照组,改功能,相当于版本1、版本2、版本x同体~~~~printf不改怎么能行呢,给我打印个X出来看看对错——X刚才就不存在~!

还有一个不用debug宏的原因就是,某种程度上,debug宏开关的理念和把printf注释掉是一样的(个人理解)。

不过借口归借口,还是写的程序太小了,大一点的工程不用就不现实了,也不够“高大上”,话说回来,频繁改、程序小就是不用的借口了么?

 

恰巧昨天看了一道C语言面试题,认为至少那个网页给的答案是错的,于是想测试一下,题目如下:

请问下面程序有什么错误?
  int a[60][250][1000],i,j,k;
  for(k=0;k<=1000;k++)
  for(j=0;j<250;j++)
  for(i=0;i<60;i++)
  a[i][j][k]=0;
  答案:把循环语句内外换一下

个人认为:错误关键在于k <= 1000的那个等于号——越界了。跟顺序没关系。

==================================================================================================================

另外,即使排除k <= 1000造成的数组越界,linux下仍然segmentation fault(core dumped),1000*250*60byte==15000000B,按估算空间没超,不知道为什么,是栈不够?反正我把1000改成100就行了~!

不过,这都是另一个或是另两个话题了,本次是对上边的那个“答案”质疑,想办法证明是错的,主要是为了在过程中引入debug宏。

==================================================================================================================

版本1:贴“答案”给出的方法,只改for循环的顺序:

        int a[60][250][1000],i,j,k;

        for(i = 0;i< 60;i++)

               for(j=0;j < 250;j++)

                       for(k = 0;k < =1000;k++)

                                a[i][j][k] = 0;

        for(i= 0;i < 60;i++)

               for(j=0;j < 250;j++)

                       for(k = 0;k < =1000;k++)

                               printf("%d\t",a[i][j][k]);

明显的不行~!

 

版本2.0:首先,需要把那个k<= 1000改成k < 1000,来验证我的想法,证明“参考答案”错误。

#include<stdio.h>

main(){
        inta[60][250][1000],i,j,k;
        for(k = 0;k< 1000;k++)
               for(j=0;j < 250;j++)
                       for(i = 0; i < 60;i++)
                               a[i][j][k] = 0;                        
        for(i = 0;i < 60;i++)
               for(j=0;j < 250;j++)
                       for(k = 0;k < 1000;k++)
                               printf("%d\t",a[i][j][k]);
}

结果segmentation fault(core dumped)了。

 

版本2.1:小修改。直接猜到了方向,把1000改成100了,这下程序运行正常。

 

版本3: k < 100,赋值语句从0改成i+j+k了,for内层加上大括号,打印也升级为可读性更强的格式~

        inta[60][250][100],i,j,k;
        for(i = 0;i< 60;i++)
              for(j=0;j < 250;j++)
                      for(k = 0;k < 100;k++){
                               a[i][j][k] = i+ j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }

这些看起来很自然,但是,如果这些都是在一个源文件内改的,那会有多麻烦,而且原来的还找不回来,可能这里有比较好的一个法子,就是/*注释代码段*/

但是那样依然比较麻烦,开关的灵活性稍差,还有不小心注释错了范围的问题。其实即使有这点问题,也依然能用,但是既然已经这么麻烦了,何不尝试引入宏,养成一个比较有用的好习惯呢?因为这只是小test,正经的程序要大得多。

 

合体版本:

#include<stdio.h>
//通过宏激活三个版本,分别是数组越界,数组不越界但是仍然core dump,和一个改小数组的可执行(完成)版本。
//#define WRONG_H_
//#define K_EQ_1000_H_
#define K_EQ_100_H_
main(){
#ifdef WRONG_H_
        inta[60][250][1000],i,j,k;
        for(i = 0;i< 60;i++)
               for(j=0;j < 250;j++)
                      for(k = 0;k <= 1000;k++){
                                a[i][j][k] = i+ j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }
#endif

#ifdef K_EQ_1000_H_
        inta[60][250][1000],i,j,k;
        for(k = 0;k< 1000;k++)
               for(j=0;j < 250;j++)
                       for(i = 0; i < 60;i++){
                                a[i][j][k] = i+ j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }
#endif

#ifdef K_EQ_100
        int a[60][250][100],i,j,k;
        for(i = 0;i < 60;i++)
               for(j=0;j < 250;j++)
                       for(k = 0;k < 100;k++){
                               a[i][j][k] = i + j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }
#endif

}


虽然是有点作用了,不过真正的debug可能耦合度低一点,代码重复度也应该小,本例中大部分代码重复(也可以只把循环中某一换用#ifdef和#endif括起来)。

既然知道改进方向,何不尝试一下,因为for顺序明显没影响,所以就统一了,降低耦合度么。不过数组定义的k等于1000和100那个没法拆,只能分开算了,我要体现出debug宏相对于注释的优势来。

首先,尝试拆分代码段,只对有区别的语句用宏分类,其次,不同宏之间可以用||来合并。不过还是有问题:不能使用下边这种只有if没有else的形式:

#include<stdio.h>
#define ORIGINAL_H_
//#define K_EQ_1000_H_
//#define K_EQ_100_H_
#define FINAL_H_
main(){
#ifdef K_EQ_1000_H_||ORIGINAL_H_
        inta[60][250][1000],i,j,k;
        for(k = 0;k< 1000;k++)
#endif

#ifdef K_EQ_100_H_ || FINAL_H_
        inta[60][250][100],i,j,k;
        for(k = 0;k< 100;k++)

#endif
               for(j=0;j < 250;j++)
                       for(i = 0; i < 60;i++){
                                a[i][j][k] = i+ j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }
}

因为i,j,k等都需要声明定义,它们在#ifdef里边,不能保证一定声明。即使你真开了那个选项,也不被允许(编译器的事儿吧)。

那么折中,为了方便,我选择了相对简化解——“ if 配 else ”,并且把条件砍少了。具体应该怎样做,应该衡量自己的情况定。

include<stdio.h>
//排除绝对错误的用法和合并不大的改动,最后焦点就在k的大小,完全体是100,测试体是1000(假设目标是找core dumped的原因)
//#define K_EQ_1000_H_
//#define K_EQ_100_H_
main(){
#ifdef K_EQ_1000_H_
        inta[60][250][1000],i,j,k;
        for(k = 0;k< 1000;k++)
#else
        inta[60][250][100],i,j,k;
        for(k = 0;k< 100;k++)
#endif
               for(j=0;j < 250;j++)
                       for(i = 0; i < 60;i++){
                                a[i][j][k] = i+ j + k;
                               printf("a[%d][%d][%d] = %d\t",i,j,k,a[i][j][k]);
                       }
}

这种方案也不是很完美,如注释所说,我简化了很多问题。不过,如果想把所有的语句和差别都弄上,也可以,不过视觉上会很乱~没有建设性,不做了,如果真想做,因为按句找差异进行拆分的思路已经有了(例如数组的声明和for循环的k部分),把其他所有想要的差异都一句一句拆分,然后给各个宏就完了。一句一句的拆分,比注释有点优越性了吧,如果这种一两句的分化特别多,成百上千行,那一句一句改是要死人的。


现在,把宏都注释掉,想要哪个版本的运行结果,只要取消注释就行了~既能运行错误版本去测试,又能运行“终极形态”完成任务,本例中宏的功能,更形象的说,是一个选择开关,不过,debug宏不就是个开关么?


虽然是比较粗糙的一种使用方式,不是“原装大厂”debug“的用法和教程,但是也算一种思路,更符合我们循序渐进的学习规律——你遇到困难或者效率低,然后解决这个障碍或者提高效率。其实最重要的还是你要在平时找机会引入各种应该掌握的功能,锻炼技巧,保持好习惯。

 


时间关系,未做过于细致的思考和验证,以及相关资料的查阅,有错误请指正,有兴趣的也可以找我讨论。

谢谢。