MFC编程中常见错误及如何解决

大家好!!在MFC以及Win32编程过程中,常常会遇到各种错误,有时候是在编译(compile)的时候,有时候是在生(build)的时候,有时候是在运行(run)的时候,有时候是在运行之后(memory leak)。如何解决这些错误呢,是一个必须要面对的问题,鄙人根据自己的经验在这里讲解一下常见的错误,以及解决方法。如果在说明过程中有不正确之处,请指出并谅解。
为了便于理解,我首先建立一个基于控制台的程序,因为控制台的程序简单,这些错误对于MFC和win32程序来说,是一样的。控制台程序结构简单,便于查看关键的代码以及错误的地方。我使用的是vs2010开发工具,windows7系统,对于那些还在使用vc6的同学们要注意了,尽快升级。

下面是我建立好的一个基于控制台(console)的程序:


第一个最简单的错误,未声明的标示符(Undeclared identifier),在编写程序过程中,我们使用的每一个变量或是对象,在使用前都必须进行声明,声明其实就是告知编译器这个变量或是对象的数据类型,这个数据类型可以是预定义的,如int,float,也可以是自定义的,如我们自己定义的一个结构体或类等。当我们使用了一个变量没有声明的时候,就会提示某个变量没有声明,现在我在我的程序中使用了一个变量i,但是我没有声明,如下:


我没有进行声明,现在我编译,出现以下错误:


如果我们在mfc以及win32的编程中出现这样的错误,就是因为某个变量我们没有声明,由于在像mfc,win32这样窗口化的编程中,代码多,程序结构复杂,面对错误,不知道如何下手,我双击这个错误,编译器就会为我们为我们指出错误。变量的声明可以是在相关的头文件(.h)中,也可以是该源文件(.cpp)中,如果是在头文件中,说明我们没有包含相关的头文件,现在就来解决这个问题,如果是在头文件,我就将这个头文件包含进来,如:



现在我在编译:


我也可以直接在该源文件中声明:


这样也是可以的,总结起来呢,就是出现某个变量未声明的错误的时候,说明我当前使用的某个变量没有声明,这种错误是一种语法错误(Syntax Error)。在mfc中,也许我们声明了一个类,然后在另外一个类中声明了这个类的对象,但是我们并没有包含进来定义那个类的头文件,结果就产生了未声明的变量的错误。但是原理都是一样的,因此,以后我们再遇到类似的错误,我们就可以从这样一个角度出发,去查找问题。也许大家在msdn libaray中总是看到这样的一个部分:


不管是mfc,还是win32中,当查找某个函数的时候,最下面总是有这样一个部分,比如,我上面这幅图,我查找的是CreateWindow函数,这里就说明了,这个函数在Winuser.h头文件中,而Winuser.h又包含在了Windows.h头文件中,如果我们没有包含Windows.h,就是用CreateWindow,也会出现类似的未声明的标示符的错误。和我上面讲的是一个道理,不同的是,这些函数的声明是系统,而不是我们自己而已。

第二种编程错误,传递的参数错误,这种错误包括参数的个数缺少,参数类型不符合,比如,我在这里我自己定义一个函数Add,它由三个参数,如下:



我在console头文件中声明了这个函数,然后在console源文件中定义了它,并且在main函数中准备调用,它本来有三个参数,现在我传递两个进去,如下:


我现在编译,看看它的错误提示:


本来这个这个函数有三个参数,但是我只传递了两个,因此出现了这个错误,这种错误就是参数的个数不匹配。下面我传递第三个参数,但是我传递的参数类型有问题,如下:


由上面可以知道,Add函数的第三个参数是一个整型指针,但是我传递一个整型变量i,现在我编译,看看什么错误,如下:


这种错误就是由于我们传递的参数与函数本身可接受的参数的类型不匹配造成的,因此,解决这样的问题,就是要从参数的类型下手,其实编译器已经很明确的告诉我们了,第三个参数出现了问题。那么我们现在来修改一下,Add函数的第三个参数是一个指针,无非就是要传递地址,那么我取i变量的栈中地址传给它,如如下:


我们也可以根据它的要求,我们就传递一个指针给它,也可以,如下:


这样也可以的,在mfc及win32的编程中呢,这种问题较为普遍,最常见的就是字符串的类型不匹配,其实在windows编程中,字符串主要就是Unicode以及ANSI字符,这两者的区别这里不做说明,但是在实际字符串的使用中,我们发现有很多种字符串,如LPCSTR,LPCTSTR,LPCWSTR,LPTSTR等等,很多,其实总结起来,要说区别就是宽字符与单字节字符的区别,在msdn中,你就可以发现,它们是通过了typedefine进行定义的,因此,不必把这些不同类型的定义都看成是完全不同的字符串。为了避免麻烦,我们传递产生的时候,可以把我们的变量声明成TCHAR,系统会为我们做自动转换。当然,这也不是万能的,具体情况下,根据情况解决问题。但是不管怎么说,对于解决这种参数类型不匹配的问题,通过这个例子,明白了它在编译器中的错误提示,为我们查找问题提供了方向。

第三种错误,是两个头文件互相包含,在我们程序中,有#include语句,它可以将某个头文件包含进来,有时候,头文件过多的时候,出现互相包含的情况,比如a.h头文件中有语句#include “b.h”,在b.h头文件中有语句#include "a.h",这样就会产生错误,这种错误在编译的时候,就会出现,这个例子,我这里就不编写了,因为我有一篇博客,说过这个问题:http://blog.csdn.net/xinzhiyounizhiyouni/article/details/11522089。这种错误,如果是第一次遇到,还真不好找。如果以后再遇到这样的错误,可以从这个方向出发,解决这个问题。

第四种错误,在说明第四中错误之前,我要在我上面的这个例子中,再添加一个项目,这个项目的作用是生成一个动态链接库(dll),然后我调用这个dll中的函数。添加之后如下:


console项目是刚才我们的,test_dll是刚才我加上去的,我在test_dll.h头文件中,声明了一个函数,并将其输出以为外部程序调用,如下:


在test_dll.cpp文件中,我进行了定义,对于dllmain函数我在这里就不贴出来了,这个基本是固定的,算了,还是贴出来吧,以免误会,如下:


在这里我声明并定义了ShowHello函数。好了,现在我点击生成,就会生成一个dll文件和一个lib文件,如下:


可以看见,我们已经成功生成了一个test_dll.dll文件和一个lib文件,现在我要在console项目中调用当中的ShowHello函数,在这个过程中来查看一下错误,现在回到刚才的console项目中,我首先把test_dll.h头文件放到console项目中,如下:


然后在console项目中的包含这个头文件,为什么要这样呢,因为test_dll.h有ShowHello函数的声明,包含之后呢,我就在main()函数中调用,如下:


然后我们点击编译,编译输出信息如下:


我们看见,编译是没有问题,是成功的,现在我们点击生成,再看看什么情况:


咦,我们看见,错误出现了,那么这个错误就是我要讲的错误,这个错误在编程过程过程中常常会遇到,那么这个错误是怎么产生的呢,编译的时候没有问题,生成的时候就出现了问题。现在我就说明一下,其实当我们点击编译的时候,编译器是在进行一个查找语法错误的过程,什么语法,就是我们的c语言,c++语言的语法,例如前面我们说到的,使用一个变量之前要声明,就是一个语法规则。当我们点击生成的时候,就是开始根据编写的代码到找到代码中使用的函数的执行部分,链接各种库,最后生成一个exe文件的过程,那我们回头看看这里,我只对ShowHello进行了声明,因此编译是可以通过,没有语法错误,但是它的执行部分在生成的test_dll.dll文件中,而编译器找不到这个文件,因此就出现了上面的错误。那么我们看刚才我们生成test_dll.dll文件的时候,还同时生成了一个lib文件,这个文件其实是一个引入库,编译器通过你使用的函数名,到lib文件中找到这个函数,而在lib文件中这个函数对应了一个地址,这个地址就是test_dll.dll文件中函数的偏移地址,通过这样,就可以在test_dll.dll文件中找到ShowHello函数。有了这个思路,我们来解决这个问题,我们把test_dll.dll文件和test_dll.lib放到console项目下,如下图:


在console.cpp文件做如下修改:


现在我再点击生成,看看还有没有错误:


现在看,就没有错误了,成功生成了一个exe文件,在mfc和win32编程中,道理是一样的,如果我们遇到这样的错误,也是出现了找到函数的执行部分,一般对于windows预定义的函数,一般不会出现这样的问题,因为这些执行部分的dll都在windows/system32目录下面,如kernel32.dll,user32.dll。编译器在寻找函数的执行部分的时候,有个目录列表,它会分别在这写目录列表中依次寻找,当查找完了所有的目录之后,仍然没有找到这个文件,就会出现上面的错误“无法解析的外部符号”,这些目录列表,首先就是当前目录(current directory),然后是system32目录,还有windows目录,还有就是编译器中设置的目录,在属性中有这样一个选项,如下:


这里设置的目录也在包含查找的目录列表中。如果你不想把你的dll放到system32下面,就可以在这里设置dll的路径。

这里需要注意的就是,无论是缺少lib或是dll,都会出现“无法解析的外部符号”的错误提示,ilb可以在上面的库目录中设置那个lib的路径,也可以直接像上面我做的那样,在代码中添加,使用pragma comment指令。以后出现这样的错误,知道了这个原理之后,就可以从这个方向去解决问题。

第五种错误,内存泄露,在非托管环境中编程与托管环境(如C#)不同,托管代码中内存资源是由垃圾回收器自动处理回收,在非托管代码中,如c和c++环境中,我们自己分配的内存,需要由我们释放,如果不释放,这块内存就不能回收供别的程序使用,随着程序的多次运行,会使内存越来越少,最严重的情况就会导致系统崩溃,因此,一个合格的程序,应该保证没有内存泄露,还是以console项目为例子,我先把不需要的代码删掉,然后用new分配了一个内存给整型变量i,使用完了之后,我不调用delete删除内存。然后我运行,如下:


由于在控制程序中,并不像mfc中,直接就开启了内存泄露的检查,在这里,我们需要自己启用内存泄露的检查,并在出现内存泄露的时候,报告出来,因此添加了一些别的代码,好了,现在运行一下:


如此,我们看见了内存泄露的提示,并且说明内存泄露的地方在console.cpp第15行上,对于这个行数,只能做个参考,因为在mfc中,提示的行数有时候会是说wincore.cpp,这个是mfc的源文件,其实是我们自己的操作造成的这个地方的内存泄露,并不是说这个文件有问题,说到内存泄露呢,对于我们来说,对于我们分配的内存,在程序退出之前,进行删除回收就可以了,更多关于内存泄露的信息,可以查看msdn。现在我们把delete加上,再看看效果,如下:



这样就没有问题了,因此,以后再次遇到这样的错误提示,就可以从这个角度出发,查找问题。我还记得当年我学习c和c++语言的时候,听到过这样一句话“在哪里分配内存,在哪里回收”,这个观点我不太同意,在像mfc,win32这样的程序中,某个对象的指针或是变量的指针可能会不同的类中被使用,甚至是不同的线程,如果说在什么地方分配,在什么地方回收,那就不可能实现这样的应用了。要避免这样的错误,只要是要考虑我们分配之后,记得回收就可以了,但是也要注意的是,分配方式不同,回收方式不同,在c++中,可以使用new分配,用delete回收,在c函数中,可以使用malloc分配,用free释放,等等,不同的分配方式,回收方法也不同。

第六种错误,是指针使用不当产生的错误,其实指针产生的错误比较多,有的人说,也是比较难发现和解决的,其实通过调试跟踪也是可以找到错误的,或是在代码编写过程中使用ASSERT。那么在调试过程中,有问题,就会直接在ASSERT出弹出错误。指针产生的错误,很多时候会使程序直接崩溃掉。下面就来看看指针使用不当在编程中出现的错误。

首先是使用了未初始化的指针,也就是说指针还没有分配内存,就在使用了。如下:


我在这里声明一个结构体data,然后我并没有初始化,只是声明了一个它的指针D,然后我就直接使用了,像这种错误,是可以生产exe文件,并且可以运行,只有当运行到这个地方的时候,问题就会出现,不会像语法错误一样,连编译都无法通过。现在来运行一下吧,如下:


这个错误,大家也许很熟悉,呵呵,运行过程中出现一个这样的对话框,由于在这里呢,代码简单,我们一看就知道了问题所在,这样做只是为了说明产生这种错误的原因,一般出现这样的对话框,都是由于指针的使用不当产生的。现在我对其进行初始化,用new为它分配内存。如下:


这样就可以了。

下面说一种,就是指针指向的内存已经回收,我们还继续使用,也会出现这种错误,如下:


运行,也会产生错误,在mfc中,比如,我将一个类的数据成员传递给别的类使用,这个数据成员使用公有的。如果在一个类中删除了,然而又在另外一个类中继续使用,就会出现这样的错误。因此,我们这里用了一个简单的代码说明了这样一个问题。

再接着,就是滥用强制转换,照成的错误,我就遇到过,一个同学呢,他将CMainFrame对象强制转换为基于CDialog的类型,然后调用强制转换之后的方法或是变量,就会出现错误,也是弹出这样的对话框,这里说明一下。如下:


由上面看到,我声明并初始化了指针P,它的类型是整型,但是我现在对齐进行强制转换,转换成data,我自定义的结构体类型。然后进行使用。这种错误在编译和生成的步骤都可以通过,在运行的时候,问题才会凸显出来。现在我运行一下,结果程序就崩溃了,如下:


这个错误提示,大家一定也很熟悉,造成这样的错误,也是指针的使用不当造成的,在mfc和win32中,不管怎么样,都是程序资源不当造成的。但是,我这里说的这种情况,会造成这样的问题,别的问题引起的严重的错误,也会出现类似的对话框。对于具体的情况,要具体考虑。比如一个窗口句柄,由于创建失败,是一个无效的句柄,我们直接使用,也会出现类似的崩溃的情况。程序的崩溃是在运行过程中出现了严重的错误。

那么暂时错误的总结就这么多,如果还有别的错误,我会再提出来。下面我再讲讲如何对待编译,生成,运行时产生的错误,上面虽然说了那么多,但是有的错误并不是直接产生的,而是由于某个错误产生的一连串的错误。因此,我这里要说明一下,当我们编译,生成的时候,在输出窗口会如果显示了很多的错误,几十个错误,怎么办呢,其实我们可以从输出的错误提示信息中的第一个错误开始解决,因为后面的错误很可能是由于第一错误产生的。如果第一错误解决了,后面的错误也就不存在了。解决完一个错误之后,再编译,错误就会越来越少,直到最后的解决。最后,对于我们查错也是很重要的,那就是跟踪调试,这个主要是针对程序运行过程中产生的如程序崩溃的错误或其他错误,在最有可能有错误的地方,我们设置一个断点,然后一步一步运行。根据情况,我们选择逐句或是逐过程运行,至于这两个什么却别,你试一下就知道了,呵呵,很简单。根据程序代码执行的路径,一步一步运行,运行的过程中检查函数的返回值以及所使用的变量,在vs2010中,只要把鼠标放到那个变量之上就可以看见它的内存中的值,如果与我们预想的不一样,或是干脆指针就是没有初始化等等的错误,就可以找到,同时也可以找到出现错误的语句是那一句,这样就可以定位错误的位置。然后进行解决。在MFC和Win32中涉及到窗口,程序逻辑相对复杂些,这就要求我们要熟悉windows窗口化程序框架,在合适的位置设置断点进行调试。

好了,希望大家在学习编程的道路上顺利,我是“农民”;

也可以到这个网站查询编程信息和提问;http://www.panshy.com,呵呵,我也是帮别人推广的,我看其实也挺不错的,可以帮助到各位同学。呵呵


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值