MFC的CString有不少Bug,例如内存泄漏,空值判断失误等,我们最近还发现了一个新的问题,CString的内存管理在release模式采用堆的方式进行管理,不过对已经生成的堆在程序运行期间却没有释放机制。举个例子,程序在运行期间,如果需要解析文档,则需要生成的大量的CString对象,解析完毕后,程序释放了这些CString对象,但是MFC并没有释放CString所占用的内存,如果程序长期运行,则会造成大量的无效空间占用。解决办法不明,考虑了一下,可能有如下几种方案:1。在MFC的基础上创建CString内存释放函数,察看了MFC的实现代码,由于CString的内存管理类CFixedAlloc将物理内存管理的成员指针m_pBlocks、m_pNodeFree设成了protected。在外面的代码难以访问,有没有什么办法可以绕过C++的成员保护机制?2。利用std::string,或者其他代码替代CString,不过MFC中大量采用CString作为库函数的接口,而且将无法使用CStringArray(不过stl有替代方案),对已有程序修改工作量不少。同时,不知道std::string等替代方案在内存碎块问题上考虑如何?因此此方案稍候考虑。3。改写mfc中CString内存管理的实现,增加运行中无用内存释放机制,这个释放代码还好说,可是怎么让改动生效呢?重新编译mfc?怎么做?会导致什么结果?做过的朋友请介绍一下经验,没做过的朋友请多讨论,大家研究研究

解决方案 »

  1.   

    解析完毕后,程序释放了这些CString对象
    --你是怎么释放的呢?
    如果需要解析文档,则需要生成的大量的CString对象
    --为何不考虑用char*方式呢?
      

  2.   

    protected成员函数,继承的类对象可以访问。CString确实不是一个很好的类,虽然有时候它很方便。
    把CString改成char*,改动应该是最小的。MFC中大量采用CString作为库函数的接口
    ------>
    大多数这样的函数也提供了LPSTR的重载函数.
      

  3.   

    我用CString用到过内存泄漏的,
    同事写了一个函数,参数是LPCTSTR的,我想都没想,就用CString作为实参代进去了,结果内存泄漏了,查不出原因,后来,没办法,用了一个char数组,作为参数代进去,就没问题,后来成功了,也没深入研究为什么会出问题,说这个经历只是想告诉大家,大家有时候可能会调用dll中的函数,可能有些参数用CString可以代进去,编译也通过,但是可能往往就会出错,可能执行的时候就出现意想不到的问题,希望大家能多个心眼。
    另外,在vc.net中getbuffer和releasebuffer老用不对,nnd
    后来干脆就不用了
      

  4.   

    如果真觉得CString那么烂,就重写一个呀,也没想像中那么难。MFC中大量采用CString作为库函数的接口
    ---这都不是什么问题,多写几个和CString的互转、比较、拷贝构造函数不就完了
      

  5.   

    呵呵,网上有篇文章
    《CString工作原理和常见问题分析》,前几天正好翻到了,不知道大家看过了没有,没有的话,可以去研究研究http://www.kao8.cn/more.asp?name=sunsjw&id=125
      

  6.   

    to: handsomerun(毛毛)
    跨越DLL传递CString参数以前也有贴子说过要产生内存泄漏,我觉得是因为有LPCTSTR转换造成
    CString对象的引用计数在DLL函数使用完后没有正确设置造成的,不妨把DLL函数参数类型LPCTSTR改为CString&引用类型,或者调用模块和被调用模块都用char*类型,避免引用计数错误
      

  7.   

    用.Net的GC和String类来处理内存碎片问题吧。
      

  8.   

    没有人告诉过你微软的东西就一定可靠吧?呵呵,其实字符串类是一个很简单的类,CString只是一种封装而已,你可以针对TCHAR类型进行封装成自己的字符串类,添加你自己认为有用的方法,针对内存的操作自己也可以控制!
      

  9.   

    应该是用法不当的概率高一些。
    微软的工程师包装这个简单的CString应该不会出什么Bug才是.
      

  10.   

    没有吧,我用Boundschecker从来没有检查出内存问题,是楼主用法有误?
    CString确实有诸多诟病,但是也是不得已而为之,并非ms的工程师水平有问题。譬如为了让CString能支持[]操作符,就不得不多添加一个CStringData,用结构体尾部放置char*指针的办法来实现动态数组和[]操作符支持。《深入解析MFC》对于CString的机制和为什么这样实现讲的很详细,虽然有些实现方法比较“龌龊”,但是ms不至于把bug多多的东西放出来给大家用,要用好它,还是先理解好它背后的思想吧。
      

  11.   

    很喜欢用CString,但是感觉大家总说他不好,心理怪不好受的,因为我还没有遇到内存泄露的情况啊:(
      

  12.   

    我也曾怀疑过CString,但后来发现都是自己使用方法上的问题。
      

  13.   

    晚上没有上来,没及时回复各位朋友,还请多多原谅,呵呵针对一些朋友说的可能是使用不当造成的,我们也考虑了这个原因,然后分析了mfc的代码,可以确信这是CString本身的内存管理机制造成的,有兴趣的朋友,可以察看一下CString的析构函数。可以看出debug版本和release版本对小与512长度的字符串的不同处理。同时,这个问题不是内存泄漏,而是CString的内存管理类分配内存堆,但在堆无效时却不作释放操作,造成占用了大量内存,却没有使用。在接下去的处理中,CString会复用这些无效的内存堆,但是数据程序处理峰值时内存占用远远大于正常运行时候,在我们的程序中,debug和release版本在同样序列的操作后,Release版本占用内存会高出debug版本近20M空间,其中还不计release版本本身就比debug内存占用空间小的因素。
    在规模较小的程序,或者非大量数据处理的程序,这个问题基本上可以忽略不记。使用char*和更换CString的方法,我们也想过(见我发的贴子,第二个方案),除了需要对已有的程序作修改外,char*还会带来内存碎片的问题,ms给CString写那么罗嗦的一个内存管理机制,不过是无聊之作,更换CString我们目前已知的只有std::string(不过还没有去平衡质量如何),上面jiangsheng(蒋晟.MSMVP2004Jan)说的.net String是一个不错的主意,我们要去评估一下,谢了,呵呵随便说一句,可能会不合某些朋友的说话,还请见谅,CString其实不简单,在座要是那位朋友能够写出来(copy就不算了,那我也会,呵呵),在下佩服的五体投地,送分200(想不出其他的法子了,200也是我最多能给的了),特别是如此兼容性和适用性,一段能够用上n年的代码,随便写出来是不大可能的。可行的方法,是在目前CString,std::string以及上面朋友的说的.net String的基础上,补充一下不足之处,互相复制点代码,呵呵对已有程序改动最小的做法,是第一种方案(增加释放接口)和第三种方案(修正CString的内存释放问题),不过第一种方案怎么绕过C++的保护机制呢,CString的内存管理类机制,我们很难继承的方法来修改。第三种方案可能需要build mfc的库,有哪位朋友作过?请指教。
      

  14.   

    我曾经试过在LPCTSTR转换传参时产生内存泄漏。但其实还是可以避开的,当时好像是先char *,再CString(),然后就解决了,当时也没怎么深究。但我觉得关键应该还是用法问题,CString还是很好用的,它提供的函数都带来了方便,不能因为一点点BUG就不用他吧?
      

  15.   

    to happyparrot(快乐鹦鹉)
    关于释放问题:还是以解析文档为例,可以在解析函数中产生大量的函数局部变量类型的CString,函数执行完毕后,这些变量自然就被释放了。其它的方式还有很多,但方式基本上都是如此。我可以确信这些处理中不会出现new出的内存没有delete的问题,这个原因就先谢谢朋友的提醒,不做考虑了。to handsomerun(毛毛) 
    CString的内存泄漏我也不是很清楚,网上的评论较常见的说法是说内部引用计数有关,在频繁使用CString指针或者引用时,可能会诱发这个Bug,不过我没遇到过,我们程序中的dll有不少导出变量和函数参数都是CString的,呵呵
    那篇文章我也查到了,不过跟我目前调查的问题稍有区别,没有认真看,呵呵to xiao_fang(frank) 
    重写?这个,我没那么厉害了,兄台也可以看看MFC中CString的实现,好像ms很闲的样子,写了那么多垃圾!笑笑而已,别当真。to oyljerry(【勇敢的心】→ ㊣回家≡赞√㊣)
    嗯,这是一个很容易出错的点,大家都需要注意点,呵呵to vcleaner(我没当大哥很久了.......) 
    CString是有一些问题,不过用起来还是不错的,我们程序也写了不少年,但哪一块拿出来估计都比不上CString的质量,呵呵to AntonlioX(做人要厚道) 
    线程不安全?怎么说,请多多指教,大家学习学习to codewarrior(会思考的草) 
    嗯,最初发生这个现象,我们作了很多推测,都失败后,才逐步怀疑到CString上面,仔细看了mfc的似现代码后,才确认这个问题。不过这个情况应该属于比较特别的应用了,如果我来设计CString的版本,这个地方估计也不会留出这么一个接口。不过总体来说,CString还是一个非常不多的字符串类。to linestyle(linestyle) 
    我也很喜欢用CString,std::string和char*我们也在用,不过用的不是一般的郁闷。to Mr_Ldh(V1971.4)
    我们最初也对自己的结论很不自信,仔细察看了CString的实现代码,才作词定论的,有兴趣我们可以讨论讨论:)
      

  16.   

    看CString的代码,遇到一个很好玩的宏,大家可以看看什么意思:#define ROUND(x,y) (((x)+(y-1))&~(y-1))
    #define ROUND4(x) ROUND(x, 4)出处strcore.cpp 72行。这个宏我到现在还没有弄懂到底是什么玩意,只能从上下文和字面意思推测出出一个结论。大家有兴趣可以研究研究
      

  17.   

    郁闷 TOO就是那么回事了...
      

  18.   

    和所有动态内存分配的类一样,std::string也有内存碎片问题。解决的办法一个是用GC(对于VC来说自然的选择是编写托管和非托管混合程序),另一个是调用HeapCompact。
      

  19.   

    Kerrie() 那个是内存对齐用的,x : 原字节数,y:对齐字节.
    返回大于x的最小的y的整数倍
      

  20.   

    我遇到一个问题,在多线程环境下用CString作为形参,偶尔会有未处理异常,如果换成LPCTSTR就不会有这个问题。
      

  21.   

    显然,MFC不是线程安全的,CString当然也不是。
      

  22.   

    CString的析构函数之所以这样写,是基于效率的考虑(当然,效率能提升多少就不好说了,就象MS把CView的IMPLEMENT_DYNCREATE和DECLARE_DYNCREATE放到不同的文件中以更改库链接粒度以降低link进项目的冗余代码量一样),CString对于buffer的分配和释放,均以64、128、256、512为单位进行,这使我想到了ms在file system中使用的lazy write方案和驱动中经常使用的lookside list。我的想法是,ms在设计的时候可能认为string是一种经常要动态调整大小的类,尽管x86是段页式离散内存管理,但是一个页4K,一个CString大部分时候都占用不了那么多空间,如果象debug版那样FreeData即等于delete buffer,一段时间下来堆中会留下很多内部碎片,这对于内存管理非常不利,所以才把它设计成这个样子。
    另外,我查看过.net的CString,实现方法有了很大改变,增加了AfxStringMgr类。时间仓促没有仔细看,可能是解决了楼主的问题。虽然重编译MFC库是可以的,但是这样就丧失了dll的初衷,不值得提倡。
    最后我有一个建议,既然知道了MFC的CString对内存的管理是以64、128、256、512进行,而且你的程序会大量生成CString对象,为什么你就不能在程序里做到每次要用到CString的时候,确保其中字符个数大于512或者正好等于64、128、256、512呢?只要是大于512的CString,FreeData的动作正是你想要的,反过来说,即使你不使用CString,而是一下子new出1000个长度<10的char数组,照样也会增加内存消耗的。当然也可以试试别的方案,譬如.net的新版CString及std::string。
      

  23.   

    to  jiangsheng(蒋晟.MSMVP2004Jan)
    嗯,很好的建议,不过gc我不是很清楚,先去学习学习看看该怎么做,呵呵。如果需要一个新的string类的话,把CString给复制或者派生出来,加上我们所需的改动,也是一个可行的做法。to roscoe(草上飞) 
    结合上下文,确实是这个作用,后面尝试运行了一下,发现y必须为2的整数倍,才有正确结果,真是搞怪的算法,现在还是没彻底弄懂是如何算出结果的。to codewarrior(会思考的草)
    老兄高见,在程序中分配长度大于512的字符串,在现有模式下无需要作其他的工作就可以规避掉这个问题,但如果算上已有的程序,改写起来还是一件很头疼的事情,因此我需要找出几种可行的方法评估以下,哪种方法比较合适我们现有的状况。重编译的mfc不是一个理想的办法,不过我们还是希望了解一下如何做?可能有什么后果,请指教。
      

  24.   

    在转贴之前我先提一下,rebuild MFC要求你的VC绝对不能安装Processor Pack。否则会出现link错误。
      

  25.   

    这个是M$官方的说法:Building the MFC DLLRebuilding the MFC DLL is intentionally difficult, so you think twice (or three times) before doing it. If you understand the potential packaging problems and redistribution restrictions described below, and you still really need to rebuild the MFC DLL, you can.The MFCDLL.MAK file will build the Debug DLL with CodeView info:NMAKE /f mfcdll.mak DEBUG=1 LIBNAME=MYMFC
    The MFCDLL.MAK file will build the Release DLL without CodeView info:NMAKE /f mfcdll.mak DEBUG=0 LIBNAME=MYMFC
    This will build a private version of the MFC DLL in your MFC SRC directory with the standard MFCxx.DLL and MFCxxD.DLL names. You will need to copy them to an appropriate place on your path to use the new DLLs. The MFCDLL.MAK makefile will also rebuild the import libraries (MFCxx.LIB and MFCxxD.LIB) and place them in the standard MFC LIB directory. This will replace the prebuilt MFCxx.LIB and MFCxxD.LIB libraries, so be careful.If you wish to redistribute a modified version of the MFC DLL library, be sure to change the name of the DLL in the MFCDLL.MAK makefile and the two .DEF files. See the makefile MFCDLL.MAK for more info.You may modify the library and redistribute a retail (/release) of your modified library only if you rename it to something other than MFCxx.DLL. You may not redistribute the debug version of either the pre-built or custom built debug DLL.These redistribution restrictions are primarily to avoid a proliferation of nonstandard and potentially virus containing DLLs. Ideally you should not need to rebuild the DLLs and if you redistribute your application with the prebuilt MFCxx.DLL provided with the Visual C++ product, you will avoid a lot of problems for yourself and your users.
      

  26.   

    官方全文:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_MFCNOTES_TN033.asp
      

  27.   

    非官方(CHS):http://comcamp.diy.myrice.com/techarticles/vc/0003.htm
    非官方(ENG):http://www.trigeminal.com/usenet/usenet034.asp
      

  28.   

    最后再扔一个砖头。
    如果文档很大,析构的时候我觉得还是使用数组WCHAR or TCHAR为好,对象的构造和析构都是非常消耗资源的,而且不能自由掌控。
      

  29.   

    偶还有一个想法,你可以试试把CString的实现代码从MFC里拷出来,修改之后,在你的工程里新建一个类,譬如叫CStringEx,然后#define CString CStringEx,把原来的CString代换成你修改过的,这样也不需要改动原来的代码。毕竟rebuild MFC “not a good idea”。
      

  30.   

    to codewarrior(会思考的草)
    先谢谢老兄的大力相助,看了你的文档后,想法和你不谋而合,呵呵,不过还是要挣扎着看下去的,起码得给其他人一个放弃的说法。研究研究先,有什么问题再向你请教。
      

  31.   

    不是CString的Bug,或许是你的场合不适合用CString,软件(或模块)都有一定的适用性的,不能说不符合你的需求就是有bug。比如不能说win98防不住CIH就是win98的Bug。
      

  32.   

    我也遇到过这个问题,那是CString本身的问题,不过在VC.net中已经修改了这个bug.我现在就用它了。
      

  33.   

    to ipgk(loboho)
    这个?我门就不用作争论了,反正目的是要解决这个问题,对吗?呵呵to jnifusun(求你整死我)
    建议你看看CString自身的实现,这个问题不是一个明显的错误,很难一两句代码能说清楚,有兴趣的话我们可以讨论讨论to ervinsas(sas)
    羡慕,我们还在做迁移平台的评估工作呢,目前是无望了。
      

  34.   

    呵呵,总之讨论下来结果有4:
    1,改用.net的CString或者使用托管。
    2,rebuild MFC
    3,rewrite CString and repalce it。
    4,确保每次字符个数>512。目前好像就这么多,呵呵。
      

  35.   

    很郁闷的。
    CString只能一般用用
      

  36.   

    折腾了半天,终于编译通过了,不过我自己的机器居然提示找不到afx.h,可能跟我安装vc时出了几个错有关。如codewarrior(会思考的草)所说,我们目前能考虑的方案如下:
    1,改用.net的CString或者使用托管。
    ——可行,但需要环境的支持,我们目前没这个条件了
    2,rebuild MFC
    ——可行,风险不低
    3,rewrite CString and repalce it。
    ——可行,已有代码有相当的工作量
    4,确保每次字符个数>512
    ——对我们的程序来说,内存消耗较难以接受,而且还得修改原有代码,呵呵这里感谢各位朋友的像助,特别是codewarrior(会思考的草)的大力支持,散分。
      

  37.   

    西西,把MFC某类挖出来在我的项目里改写的事情偶经常干……
      

  38.   

    我觉得楼主说的应该不能说他是一个Bug. 只是一策略问题. 
    MFC不回收内存肯定是有他的道理的. 比如考虑到效率问题等等.你如果要涉及大规模的文字处理,当然要自己管理你的内存了, 你使用CString不是等于
    把自己的命运交给别人了吗.