写给初学者:一个调试、利用msdn的经典例子
编程序最重要的是什么?除非你是天才,否则的话,我认为最重要的是学会调试、找出问题所在并解决问题,而不是一开始就写出一些成功的代码,甚至一般人看不懂的代码(这些人一般都是天才,当然,也有可能是写了无数次的熟练工)。看过n多的人学了几年编程都不会调试,也罗嗦了n次,现在正好copy一个别人的代码,其中解决一个小错误的过程希望拿来让初学者学习。既然是给初学者学习的,当中不会有太复杂的东西,不会有复杂的调试过程,甚至可以说没有逻辑错误。
好,开始!环境为vc6.0,现象就是编译没有错误,运行出错(初学者最经常这么说了),具体来说就是运行程序(对话框工程),点击一个按钮,然后弹出一个错误对话框:”0x00401422”指令引用的”0x00000008”内存。该内存不能为”read”。从这个对话框,你能指望初学者看出什么东西呢?不会调试的话,还能怎么解决呢?
1,
加断点找出错误代码行
既然是点击按钮就出错,不妨认为出错的代码在该按钮所在的响应函数,最简单的就是每一行都按F9加上断点,当然,如果代码很多行,也可以隔若干行加一个断点然后F5开始调试。F5调试时程序会运行到断点所在行之前的一行,然后停在断点所在行,如下所示,数字代表代码所在行:
01
:
struct
protoent *ppe;
断点A
02
:
ppe=getprotobyname(
"tcp"
)
03
:
m_s=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
04
:
if
(m_s==INVALID_SOCKET)
05
:
{
06
:
MessageBox(NULL,
"socket()
函数执行失败
!"
,
"
错误
"
,MB_OK);
07
:
return
FALSE;
08
:
}
断点B
09
:
return
TRUE;
当程序运行,停在断点A时,继续F5调试,此时出来一个出错对话框:
Unhandled exception in test.exe : 0xC00000005 : Access Violation,刚入门的最怕这个错误了吧?看不懂?基础差?英语不过关?都没关系(只是暂时没关系),借助英汉字典或者翻译软件你最起码知道Unhandled exception的大概意思是“未处理的例外情况,异常”(注:这些翻译我都是特意找软件翻译的),翻译不准确没关系,大概知道是这个意思就行了;0xC00000005你应该猜测到是内存地址,猜不到也不重要;Access Violation按照前面说的你也应该知道大概是“访问违例”的意思;整个句子的意思大概就是出现了非法访问某个内存地址的异常。知道了出错的现象了,我们就可以检查断点A到断点B之间的代码,此时最起码可以知道出错的代码行是02到08,09还没有运行。其实我们也无需一个一个的去检查,shift+F5停止此次debug,然后重新开始直到断点A,改用F10单步执行(即使没有断点也会一行一行的执行代码),我们发现那个黄色的箭头移动到03(表示02已执行,03准备执行),一切正常,然后继续F10执行03,错误现象重现,错误代码行在03。
2,
检查错误代码行
我们分析错误代码行:
m_s=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
当然也要分析前面跟它联系密切的行。
m_s
是一个变量,
socket
是一个函数(你甚至不需要弄懂它是干什么的,)
PF_INET
和
SOCK_STREAM
是系统
define
的一个东西(把光标放置在它上面
vc
就会有提示),可以理解是系统给出的一个固定的值,显然也不容易出错,剩下最后一个参数
ppe->p_proto
,一个指针指向一个变量。指针?出错的地址?如果你学过
C/C++
这两个东西你应该联想的到。难道是这里出了问题?先看
ppe
由
01
行定义,自然不会错,由
02
行赋值,此时我们应该可以怀疑是否赋值成功,也就是说
getprotobyname(
"tcp"
)
是否产生了一个正确的返回值并赋值给
ppe
?同样通过调试解决问题。
程序运行到断点
A
,此时我们应该看看
ppe
的值是什么,可以在
variables
窗口(如果没有,在
vc
菜单栏点右键选择
variables
打开)看到它的值是
0xcccccccc
,这个值是什么,怎么得来的现在不用管。尽管
variables
窗口可以查看到它的值,我还是建议你在
watch
窗口亲自输入查看,因为
variables
窗口只能查看当前相关的变量的值。如果你的
vc
没有显示
watch
窗口,使用打开
variables
窗口的方法打开,然后双击
name
列下面的单元格输入
ppe
就会显示它的值。
F10
执行
02
行,此时发现
ppe
的值是
0x00000000
,这是一个空指针,
02
行相当于
ppe=NULL;
我们在
03
使用了
ppe->p_proto
自然会出错。所以,虽然
02
行正确运行,
03
行出错,但其实我们要修改的是
02
行,因为
02
行正确运行但得出一个错误的结果。
3,
使用
MSDN
虽然知道
getprotobyname(
"tcp"
)
出错,但是我们不知道
getprotobyname函数是干什么的,好办,把光标放置在这个函数上面,按F1调出MSDN(我一直没有更新,用的是随vs6发行的那个版本),直接找到getprotobyname。既然我们知道了它返回一个相当于NULL的东西,我们就先看它的Return Values(其它内容可以不看,尽管我不推荐你这么做,但在这里你可以暂时这么做):
Return Values
If no error occurs, getprotobyname returns a pointer to the PROTOENT. Otherwise, it returns a NULL pointer and a specific error number can be retrieved by calling WSAGetLastError.
大概意思是说:如果没有错误发生,它返回一个指向PROTOENT的指针,否则返回一个空指针和一个特定的错误数字,该错误数字可以通过调用
WSAGetLastError获得。
现在只要我们获得这个错误数字,就可以知道错误发生的原因了,因为MSDN紧接着列出了Error Codes:
Error Codes(这里只列出2个说明问题)
WSANOTINITIALISED
|
A successful
WSAStartup must occur before using this function.
|
WSAENETDOWN
|
The network subsystem has failed.
|
很可惜这里用WSANOTINITIALISED这些东西来代替了数字,不管如何,先得到数字再说在02行代码后面添加一行代码:
int
nErrno=WSAGetLastError();
然后继续调试,执行完
02
行后,光标停在新的
03
行代码,继续
F10
,得到
nErrno
的值
10093
,也就是错误数字,这个数字表示什么意思呢?怎么和
Error Codes
联系起来?还是要利用
msdn
,在
msdn
的索引里输入
Error Codes
,双击第一项,出来很多
Error Codes
的主题,我们这个程序是
socket
相关,当然是找位于
Windows Sockets
的那一个啦,双击进入。好好看看第一段:
The following is a list of possible error codes returned by the WSAGetLastError call, along with their extended explanations. Errors are listed in alphabetical order by error macro. Some error codes defined in WINSOCK2.H are not returned from any function - these have not been listed here.
其实不看关系也不是很大,主要是找到10093:
WSANOTINITIALISED
(10093)
Successful WSAStartup not yet performed.
Either the application hasn't called
WSAStartup or
WSAStartup failed. The application may be accessing a socket which the current active task does not own (i.e. trying to share a socket between tasks), or
WSACleanup has been called too many times.
这一段应该不难看懂吧?实在不行就找东西翻译,再结合前面那个表提到的Error Codes,我知道了原来是我copy代码时露掉了执行WSAStartup的这一段。
4,
修正错误
首先把露掉的代码copy过来:
///
初始化
Socket
函数库
int
err;
WORD wVersion;
WSADATA WSAData;
wVersion=MAKEWORD(2,0);
err=WSAStartup(wVersion,&WSAData);
if
(err!=0)
{
AfxMessageBox(
"
无法装载
Socket
库
."
);
}
if
(LOBYTE( WSAData.wVersion ) != 2)
{
AfxMessageBox(
"
无法找到合适的
Socket
库
."
);
WSACleanup();
}
同样的,一些收尾的工作也加上(这些都不是本篇文章的重点了):
///
清除
Socket
库
WSACleanup();
原先的错误代码也要修改,修改
02
行即可,一个对于初学者来说最朴素的修改是这样的:
if
(!(ppe=getprotobyname(
"tcp"
)))
{
int
nErrno=WSAGetLastError();
CString strErrno;
strErrno.Format(
"getprotobyname()
函数执行失败
,
错误代号是:
%d"
,nErrno);
AfxMessageBox(strErrno);
return
FALSE;
}
费了很大的劲写这篇文章,希望能给初学者一点启发(最起码不能有误导,哪位高手要是感觉有误导的倾向,请提出宝贵意见),下次再有人问我调试是怎么回事事,我就给他看这篇文章。
路过,顶一下。
论坛里经常看到有人问程序崩溃了我该怎么办,还能怎么办?加断点找呗。正如本文所说:
1 加断点找出错误代码行
论坛里经常看到有人问程序崩溃了我该怎么办,还能怎么办?加断点找呗。正如本文所说:
1 加断点找出错误代码行
程序崩溃了经常会给出一个地址,从那个地址经常就可以能找到对应的出错的地方。
有时候根本不知道啥地方错。
有时候根本不知道啥地方错。
# re: 写给初学者:一个调试、利用msdn的经典例子 2006-06-14 20:14 sjdev
MSDN再加上网络,我相信几乎没有解决不了的调试错误!
不知道兄台注意到没有:几乎所有的错误信息,放到baidu里面
查找,都可以找到很多相关例子。