以人为主的测试,到以测试用例为主的测试用例

发布新日志

  • cmd外部命令

    2007-07-17 10:11:24

    复制F:盘下a这个文件夹的文件到F:盘b这个文件夹就可以这样写
    String command = "cmd.exe /c start copy f:\\a f:\\b";
    Runtime.getRuntime().exec(command);

  • 检测并排除内存泄漏

    2007-06-21 21:33:45

     

     摘要:
     本文描述了如何使用VC++和CRT库提供的工具定位和排除内存泄漏,检测的难度使得使用C/C++编程语言的应用开发产生问题。
     介绍:
     动态分配、回收内存是C/C++编程语言一个最强的特点,但是中国哲学家孙(Sun Tzu,我不知道是谁?那位知道?) 指出,最强的同时也是最弱的。这句话对C/C++应用来说非常正确,在内存处理出错的地方通常就是BUGS产生的地方。一个最敏感和难检测的BUG就是内存泄漏-没有把前边分配的内存成功释放,一个小的内存泄漏可能不需要太注意,但是程序泄漏大块内存,或者渐增式的泄漏内存可能引起的现象是:先是性能低下,再就是引起复杂的内存耗尽错误。最坏的是,一个内存泄漏程序可能用完了如此多的内存以至于引起其他的程序出错,留给用户的是不能知道错误到底来自哪里。另外,一个看上去无害的内存泄漏可能是另一个问题的先兆。幸运的是VC++DEBUGER和CRT库提供了一组有效的检测和定位内存泄漏的工具。本文描述如何使用这些工具有效和系统的排除内存泄漏。

     启动内存泄漏检测:
     主要的检测工具是DEBUGER和CRT堆除错函数。要使除错函数生效,必须要在你的程序中包含以下几个语句:
    #define _CRTDBG_MAP_ALLOC
    #include <stdlib.h>
    #include <crtdbg.h>
     并且这些#include 语句必须按上边给出的顺序使用。如果你改变了顺序,可能导致使用的函数工作不正常。包含crtdbg.h的作用是用malloc和free函数的debug版本(_malloc_dbg 和 _free_dbg)来替换他们,他们能跟踪内存分配和回收。这个替换仅仅是在debug状态下生效,Relese版本中还是使用普通的malloc和free函数。
     上面的#define语句使用crt堆函数相应的debug版本来替换正常的堆函数。这个语句不是必需的,但是没有他,你可能会失去一些有用的内存泄漏信息。
     你一旦在你的程序中增加了以上的语句,你可以通过在程序中增加_CrtDumpMemoryLeaks();函数来输出内存泄漏信息。
     当你在debuger下运行你的程序时,_CrtDumpMemoryLeaks 显示内存泄漏信息在OutPut窗口的Debug标签项里。内存泄漏信息举例如下:

    Detected memory leaks!
    Dumping objects ->
    C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.cpp(20) : {18}
       normal block at 0x00780E80, 64 bytes long.
     Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
    Object dump complete.
    如果你没有使用 #define _CRTDBG_MAP_ALLOC语句的话,输出信息将如下:

    Detected memory leaks!
    Dumping objects ->
    {18} normal block at 0x00780E80, 64 bytes long.
     Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
    Object dump complete.
     像你所看到的,当_CRTDBG_MAP_ALLOC 被定义后_CrtDumpMemoryLeaks给了你很多有用的信息。在没有定义_CRTDBG_MAP_ALLOC 的情况下,显示信息包含:
    1.内存分配的编号(大括弧中的数字);
    2.内存快的类型(普通型、客户端型、CRT型);
    3.16进制表示的内存位置;
    4.内存快的大小;
    5.前16bytes的内容。
     如果定义了_CRTDBG_MAP_ALLOC ,输出信息还包含当前泄漏内存是在那个文件中被分配的定位信息。文件名后圆括弧中的数字是行数。如果你双击这行信息,
    C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.cpp(20) : {18}
       normal block at 0x00780E80, 64 bytes long.
    光标就会跳转到原文件中分配这个内存的行前。选择Output中的题是行,按F4能达到同样的效果。

     使用Using _CrtSetDbgFlag:
     如果你的程序的退出点只有一个的话,调用_CrtDumpMemoryLeaks将是非常容易。但是,如果你的程序有多个退出点话会是什么样一个情况?如果不想在每个退出点都调用_CrtDumpMemoryLeaks,你可以在程序的开始包含以下调用:
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    这个语句会在你的程序结束时自动调用_CrtDumpMemoryLeaks,但是你必须象前边提到的那样设置_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF这两个标志位。

     介绍一下内存块的类型:
     就象前面指出的,一个内存泄漏信息指出每个内存泄漏块的类型为普通、客户端或者CRT型。在实际程序中,普通型和客户端型式最常见的类型。
     普通型内存块是你的程序平常分配的内存类型。
     客户端型内存块是MFC程序给需要析构的对象分配的内存块。MFC的new操作可以选择普通型或客户端型中合适的一种作为将要被创建的对象的内存块类型。
     CRT内存块是CRT库为自己使用而分配的内存块。CRT在处理自己的释放内存操作时使用这些块,所以在内存泄漏报告中这种类型并不常见,除非发生严重异常(例如:CRT库出错)。
     还有两种类型你在内存泄漏信息中看不到:
     自由块,它是已经被释放的内存块;
     忽略块,它是已经被特殊标示的内存块。

     设置CRT报告的格式:
     在默认情况下,_CrtDumpMemoryLeaks输出的内存泄漏信息就象前边描述的那样。你可以使用_CrtSetReportMode让这些输出信息输出到其他地方。如果你使用一个库,它可能要使输出信息到其他的地方,在这种情况下,你可以使用_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );语句使输出信息重新定位到Output窗口。

     根据内存分配编号设置断点:
     内存泄漏报告中的文件名和行数告诉你内存泄漏的位置,但是知道内存泄漏位置不是总是能找到问题所在。在一个运行的程序中一个内存分配操作可能被调用多次,但是内存泄漏可能只发生在其中的某次操作中。为了确认问题所在,你除了知道泄漏的位置之外,你还必须要知道发生泄漏的条件。内存分配编号使得解决这个问题成为可能。这个数字就在文件名、行数之后的大括弧内。例如,在上面的输出中“18”就是内存分配编号,它的意思是你程序中的内存泄漏发生在第18次分配操作中。
     CRT库对正在运行程序中所有的内存块分配进行计数,包括自身的内存分配,或者其他库(象MFC)。一个对象的分配编号是n表示第n个对象被分配,但是它可能并不表示第N个对象通过代码被分配(在大多数情况下它们并不相同)。
     你可以根据内存分配编号在内存被分配的位置设置断点。先在程序开始部分附近设置一个断点,当你的程序在断点处停止后,你可以通过QuickWatch对话框或者Watch窗口来设置内存分配断点。在Watch窗口中的Name列中输入_crtBreakAlloc,如果你使用的是多线程DLL版本的CRT库的话你必须包含上下文转换 {,,msvcrtd.dll}_crtBreakAlloc。完成后按回车,debugger处理这次调用,并且把返回值显示在Value列中。如果你没有设置内存分配断点的话返回值是-1。在Value列中输入你想设置的分配数,例如18。
     你在自己感兴趣的内存分配位置设置断点后,你可以继续debugging。细心的运行你的程序在相同的条件下,这样才能保证内存分配的顺序不致发生变化。当程序在特定的内存分配处停下来后, 你可以查看Call 窗口和其他的debugger信息来分析此次内存分配的条件。如果有必要你可以继续运行程序,看一看这个对象有什么变化,或许可以得知为什么内存没有被正确的释放。
     尽管这个操作非常容易,但是如果你高兴的话也可以在代码中设置断点。在代码中增加一行代码_crtBreakAlloc = 18;另外也可以通过_CrtSetBreakAlloc(18)来完成设置。

     比较内存状态
     另一个定位内存泄漏的方法是在重要位置捕捉应用程序的“内存快照”。CRT库提供了一个结构体类型 _CrtMemState,使用它你可以保存内存状态的快照(当前状态)。
    _CrtMemState s1, s2, s3;
     为了得到一个快照,可以把一个_CrtMemState 结构体传给_CrtMemCheckpoint 函数,这个函数可以把当前的内存状态填充在结构体中:
    _CrtMemCheckpoint( &s1 );
     你可以通过把结构体_CrtMemState 传给_CrtMemDumpStatistics函数来输出结构体中的内容。
    _CrtMemDumpStatistics( &s3 );( &s1 );
     它输出的信息如下:
    0 bytes in 0 Free Blocks.
    0 bytes in 0 Normal Blocks.
    3071 bytes in 16 CRT Blocks.
    0 bytes in 0 Ignore Blocks.
    0 bytes in 0 Client Blocks.
    Largest number used: 3071 bytes.
    Total allocations: 3764 bytes.
     为了得知一段代码中是否有内存泄漏,你可以在这段代码的开始和完成处分别拍一个快照,然后调用_CrtMemDifference函数来比较两个状态:
    _CrtMemCheckpoint( &s1 );
    // memory allocations take place here
    _CrtMemCheckpoint( &s2 );

    if ( _CrtMemDifference( &s3, &s1, &s2) )
       _CrtMemDumpStatistics( &s3 );
     就像名字中暗示的那样,_CrtMemDifference比较两个内存状态,并且产生一个结果(第一个参数)。把 _CrtMemCheckpoint 放在程序的开始和结尾,调用_CrtMemDifference 来比较结果,这也是一种检测内存泄漏的方法。如果发现内存泄漏,你可以使用_CrtMemCheckpoint把程序分成两半分别使用上述方法来检测内存泄漏,这样就是使用二分法来检查内存泄漏。

  • C/C++内存泄漏

    2007-06-21 21:28:06

        对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题,比如Smart Pointer,Garbage Collection等。Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题;Garbage Collection技术在Java中已经比较成熟,但是在c/c++领域的发展并不顺畅,虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的,作为一个c/c++程序员,内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在,找出发生问题的代码。

      内存泄漏的定义

      一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:

    void MyFunction(int nSize)
    {
     char* p= new char[nSize];
     if( !GetStringFrom( p, nSize ) ){
      MessageBox(“Error”);
      return;
     }
     …//using the string pointed by p;
     delete p;
    }

      例一

      当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。

      广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。

      GDI Object的泄漏是一种常见的资源泄漏:

    void CMyView::OnPaint( CDC* pDC )
    {
     CBitmap bmp;
     CBitmap* pOldBmp;
     bmp.LoadBitmap(IDB_MYBMP);
     pOldBmp = pDC->SelectObject( &bmp );
     …
     if( Something() ){
      return;
     }
     pDC->SelectObject( pOldBmp );
     return;
    }

      例二

      当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。

      内存泄漏的发生方式:

      以发生的方式来分类,内存泄漏可以分为4类:

      1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。

      2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回 True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

      3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:

    char* g_lpszFileName = NULL;

    void SetFileName( const char* lpcszFileName )
    {
     if( g_lpszFileName ){
      free( g_lpszFileName );
     }
     g_lpszFileName = strdup( lpcszFileName );
    }

      例三

      如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。

      4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:

    class Connection
    {
     public:
      Connection( SOCKET s);
      ~Connection();
      …
     private:
      SOCKET _socket;
      …
    };

    class ConnectionManager
    {
     public:
      ConnectionManager(){}
      ~ConnectionManager(){
       list::iterator it;
       for( it = _connlist.begin(); it != _connlist.end(); ++it ){
        delete (*it);
       }
       _connlist.clear();
      }
      void OnClientConnected( SOCKET s ){
       Connection* p = new Connection(s);
       _connlist.push_back(p);
      }
      void OnClientDisconnected( Connection* pconn ){
       _connlist.remove( pconn );
       delete pconn;
      }
     private:
      list _connlist;
    };

      例四

      假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的 Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。

      从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

  • 资源泄漏

    2007-06-21 14:52:20

    重复执行可能存在资源泄漏的操作,这一点非常关键。测试前首先应该分析一下,程序的哪里可能存在资源泄漏,然后重复大量的执行这些操作,操作的同时可以查看“windows任务管理器”中相应测试计数器检查是否存在资源泄漏。如果程序存在资源泄漏,但是在测试时没有执行存在资源泄漏的代码,也是不能发现资源泄漏的。

    资源泄漏判断标准:资源使用的一般步骤是申请资源--使用资源--释放资源。如果程序使用后没有及时释放资源,程序占用的资源就会越来越多,而系统的资源是有限的,当系统的资源被耗尽时,系统就会因为资源不足而出错。如果在重复执行某一操作时,程序占用的资源持续增加。
  • 内存泄漏

    2007-06-21 14:50:50

    内存泄漏的定义

      一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用mallocreallocnew等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用freedelete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了

    广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLEGDI ObjectSOCKET Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。

     

    内存泄漏的发生方式

      以发生的方式来分类,内存泄漏可以分为4类:

      1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
      2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。
      3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。

    4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

     

    从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

Open Toolbar