写给初学者:一个调试、利用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:}
断点B09: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;
}