一个调试、利用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:

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;
         }

posted @ 2008-05-29 11:33  徐影  阅读(849)  评论(0编辑  收藏  举报