落花时节的笔记 https://bbs.21ic.com/?425605 [收藏] [复制] [RSS] 我的技术基地!

日志

写可调试的代码

热度 1已有 2471 次阅读2008-4-23 07:49 |系统分类:单片机| vc, keil, debug

 



    夜里,梦见自己在国外的墓地。用心灵感应和这个古老的智能建筑交流。我把它打开,藏在里面躲避别人,进入远古时代。终究没有出来,化为一堆白骨。。。。。。突然想到,我该写点东西了,在这个黎明的早晨。
    题记:做程序员要做艺术家。我最近做了一段时间的代码移植工作,从2个单片机到另外一种单片机的程序组合。我觉得,我不是在剪画布拼凑,而是在捡垃圾。我强烈的欲望要做艺术家而不是清洁工。
    写可调试的代码。尤其是在标准C语言和X86处理器。我想,这是大家学的最普通的语言和是最常用的处理器。但是,如何使用好这两种工具,确实太多人还不清楚的。没有汇编功力,难以推测特别的问题。经验,有时又很重要。在没有经验和汇编的时候,我们如何去写可以DEBUG的代码?
一.细心应对程序的每一个警告。
    如果你不是高手,请从此做起。如果你写一个不到100行的代码,都警告连篇的话,我想你该拿一本书随时看看了。基础很重要。有些警告暗示了你的错误,但是编译器不确定是不是你就是希望得到不正常的运算。
    我现在用VC6为基本编程环境举一个初学者容易犯错的例子:
#include<stdio.h>
int main(){
 int i;
 scanf("%d",i);
}
这是编译器提供的警告:
Compiling...
test3.c
D:\MYC\test3.c(5) : warning C4700: local variable 'i' used without having been initialized
Linking...


test3.exe - 0 error(s), 1 warning(s)
一个本地变量i没有被初始化。
但是这段代码隐藏了2个问题:1.没有返回值。我提倡使用返回值。这可以让编译器告诉你:不是所有的退出都有返回值等问题确认你是否忘记了考虑什么。例如:switch的default处理。2.一个本地变量没有初始化。这很明显我们没有初始化。其实也不用初始化。那么这不应该是一个警告,而是一个错误。scanf典型的使用错误。但是,编译器却认为你可以就是考虑好了这么用的。
改成scanf("%d",&i);后不出现任何错误。main函数返回值现在意义不大,或许Linux还是有一定意义的。
下面举例说明为什么编译器,认为是警告而不是错误:
#include<stdio.h>
int main(){
int i,p;
p=&i;//注意:本程序在32bitCPU使用。
scanf("%d",p);
printf("%d",i);
}
p是整型,cpu是32位,这个没有问题。编译器警告:
D:\MYC\test3.c(5) : warning C4047: '=' : 'int ' differs in levels of indirection from 'int *'
说明这是一个警告,出现的不兼容问题是你自己考虑的。我已经不再检测是否都能成功执行。出现问题程序 编写者负责。
当然,我在VC+x86.32bit.cpu上没有任何问题。同样是对整型赋值。前者有误而后者正确。当然,你应该吧p改成*p来消除这个警告。
二、使用DEBUG来调试你的程序。
    我认为,如果没有警告。但程序也很有可能不正确。debug是你最常用的工具。它可以帮助你找到执行过程中的错误。废话不说,来个例程:
#include<stdio.h>


void main(){
int *p;
p=0;
//scanf("%d",p);这里用它不出现错误,估计是特殊处理过。
(*p)=0x34567890;
}
很好,任何警告都没有。大家可以运行一下。的确是常见的
“test3.exe 遇到问题需要关闭。我们对此引起的不便表示抱歉。”
呵呵,欺负微软的感觉真爽。
这用林锐的话说就是野指针,我给他加个定语:初始化过的野指针。他可以让你知道这个野指针你使用了,请检查程序流程为什么错了。可以看一下错误报告,例外:0xc0000005,这是访问未允许的空间的错误。如果用F10,按3次,到(*p)处,就会提示访问违例。问题解决。
三。如何让程序调试容易。
    1.在关键地方下断点,用F5执行,判断错误发生的区间,然后取消其他断点,精确寻找。
    2.合理书写,容易调试。
    第2点太太重要了。所以这里详述一下:这里我不使用全部代码。因为这个问题在dev-c++,和vc上都出现过,并且程序执行死机。调试受阻!
下面这个例子来自北京大学poj校内使用版1077题。校外版可能题号有区别
http://poj.grids.cn/problem?id=1077
const int MAX = 363880;
bool flag[MAX];
for(i = 0; i < MAX; i++)
 flag = false;
上面这段代码如果用F10的话,估计直接费了,必须用在他下一行下断点,然后用F5执行过去。
推荐这样写for(i = 0; i < MAX; i++) flag = false;
可以认为是一条程序,一步执行过去。既然我推荐就是可以用。这种写法也来自一个ACM进入08世界总决赛的朋友。但是,却发生了死机这种情况。程序编译结果直接有问题。就是说F10过不去。。。然后点停止,发现i出现负值。一个整型怎么也不至于这么快就溢出。然后我分成2行,编译没问题,然后吧const int 改成#define MAX 3638800;也没有问题,最后改会去也没有问题了。这种编译问题遇到以后更改写法就可以搞定。
 (注:这种问题在X86平台上少见。在其他平台上也遇到过。例如keil2)
四。其他平台的扩展
    1.利用宏定义,做安全的调试。
 有些东西有时效性,例如:热敏打印机的加热时间。所以调试的时候尤其注意安全,也许很快,设备就废了。如何编写可调试的代码成为关键。
#ifdef DBG
保存加热状态;
加热=关;
#endif
可调试的代码
#ifdef DBG
回复加热状态
#endif
这样就不至于烧坏打印机了。当然,我不希望在keil中使用bit来保存一位,因为keil的代码不可重入,所有bit有可能复用。我遇到过,所以在此提醒一下。
    2.不要期望keil有vc这么智能。
 VC的指针只指向内存,所以很容易编译。但是单片机的指针可以指向code,idata,data,xdata,各种类型。不要认为随便写就能编译正确。这是不可能的。所以要正视警告,代码的参数严格规范,否则就会出问题。


作者qq:285508360


http://lovelytime.21ic.org


路过

鸡蛋
1

鲜花

握手

雷人

刚表态过的朋友 (1 人)

评论 (0 个评论)