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*60*4byte==60000000B,60MB左右,按估算空间照内存空间大小差得远呢,不知道为什么,是栈不够?反正我把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宏不就是个开关么?

前边的方法还不是最终形态,最终形态其实需要拆分,重构,至少要有main.c debug.c和debug.h。

 

之前blog有个测试堆栈分配的程序,就是因为没写debug宏,修改频繁,并且读起来越来越困难,现在不是有办法了么——重构,把print语句都拆出去,然后加个debug宏,打开debug宏即可,类似于这种:

#define DEBUG_H_

.......

 

#ifdef DEBUG_H_

printDetail();

#endif

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

关 于学知识的循序渐进和相互联系,既然我碰到了实例,就再说一个吧,我是分类放c文件啊,那个测试程序我放在stack路径下了,那么按归类我现在要把他放 在MacroDebug路径下,总不能复制一份吧?太老土了,关键有些时候不现实,比如慢,占空间。那么应该怎么做呢,这就用到了另一个知识点,就是 linux下的文件链接link(很简单,三分钟学会),至于用硬链接还是软链接,我觉得没必要用软的,用硬的有一个好处——备份~!防删~!万一你那个路径不需要他,在这个路径还是需要的,所以备份最妥当。

 

root@v:/usr/local/C-test# ln ./stack/stack.c  ./macroDebug/stackExample.c

root@v:/usr/local/C-test# ls macroDebug/

eg1 firstDebugMacro.c  stackExample.c

不过,这个也看怎么改了,在同一个文件内加宏做修改,硬链接的两个”文件名“可以共用原文件。但是,如果重构——把原文件分成多个.c文件和.h头文件,那么缺失了这些相关文件,原来那个就废掉了(解决方法——可以把这几个文件(再单独创建一个子路径,把子路径)都链接过去~~~)

 

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

谢谢。

 



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值