topameng's profileQuake3 启示录PhotosBlogListsMore Tools Help

Blog


    March 06

    如何编写异常安全的C++代码

    http://tech.163.com 2006-04-17 09:19:59 来源: 网易学院(广州)  网友评论0 条 论坛

    关于C++中异常的争论何其多也,但往往是一些不合事实的误解。异常曾经是一个难以用好的语言特性,幸运的是,随着C++社区经验的积累,今天我们已经有足够的知识轻松编写异常安全的代码了,而且编写异常安全的代码一般也不会对性能造成影响。
      使用异常还是返回错误码?这是个争论不休的话题。大家一定听说过这样的说法:只有在真正异常的时候,才使用异常。那什么是“真正异常的时候”?在回答这个问题以前,让我们先看一看程序设计中的不变式原理。
      对象就是属性聚合加方法,如何判定一个对象的属性聚合是不是处于逻辑上正确的状态呢?这可以通过一系列的断言,最后下一个结论说:这个对象的属性聚合逻辑上是正确的或者是有问题的。这些断言就是衡量对象属性聚合对错的不变式。
      我们通常在函数调用中,实施不变式的检查。不变式分为三类:前条件,后条件和不变式。前条件是指在函数调用之前,必须满足的逻辑条件,后条件是函数调用后必须满足的逻辑条件,不变式则是整个函数执行中都必须满足的条件。在我们的讨论中,不变式既是前条件又是后条件。前条件是必须满足的,如果不满足,那就是程序逻辑错误,后条件则不一定。现在,我们可以用不变式来严格定义异常状况了:满足前条件,但是无法满足后条件,即为异常状况。当且仅当发生异常状况时,才抛出异常。
      关于何时抛出异常的回答中,并不排斥返回值报告错误,而且这两者是正交的。然而,从我们经验上来说,完全可以在这两者中加以选择,这又是为什么呢?事实上,当我们做出这种选择时,必然意味着接口语意的改变,在不改变接口的情况下,其实是无法选择的(试试看,用返回值处理构造函数中的错误)。通过不变式区别出正常和异常状况,还可以更好地提炼接口。
      对于异常安全的评定,可分为三个级别:基本保证、强保证和不会失败。
      基本保证:确保出现异常时程序(对象)处于未知但有效的状态。所谓有效,即对象的不变式检查全部通过。
      强保证:确保操作的事务性,要么成功,程序处于目标状态,要么不发生改变。
      不会失败:对于大多数函数来说,这是很难保证的。对于C++程序,至少析构函数、释放函数和swap函数要确保不会失败,这是编写异常安全代码的基础。
      首先从异常情况下资源管理的问题开始.很多人可能都这么干过:
    Type* obj = new Type;
    try{ do_something...}
    catch(...){ delete obj; throw;}

      不要这么做!这么做只会使你的代码看上去混乱,而且会降低效率,这也是一直以来异常名声不大好的原因之一. 请借助于RAII技术来完成这样的工作:
    auto_ptrobj_ptr(new Type);
    do_something...

      这样的代码简洁、安全而且无损于效率。当你不关心或是无法处理异常时,请不要试图捕获它。并非使用try...catch才能编写异常安全的代码,大部分异常安全的代码都不需要try...catch。我承认,现实世界并非总是如上述的例子那样简单,但是这个例子确实可以代表很多异常安全代码的做法。在这个例子中,boost::scoped_ptr是auto_ptr一个更适合的替代品。
      现在来考虑这样一个构造函数:
    Type() : m_a(new TypeA), m_b(new TypeB){}
      假设成员变量m_a和m_b是原始的指针类型,并且和Type内的申明顺序一致。这样的代码是不安全的,它存在资源泄漏问题,构造函数的失败回滚机制无法应对这样的问题。如果new TypeB抛出异常,new TypeA返回的资源是得不到释放机会的.曾经,很多人用这样的方法避免异常:
    Type() : m_a(NULL), m_b(NULL){
     auto_ptrtmp_a(new TypeA);
     auto_ptrtmp_b(new TypeB);
     m_a = tmp_a.release();
     m_b = tmp_b.release();
    }

      当然,这样的方法确实是能够实现异常安全的代码的,而且其中实现思想将是非常重要的,在如何实现强保证的异常安全代码中会采用这种思想.然而这种做法不够彻底,至少析构函数还是要手动完成的。我们仍然可以借助RAII技术,把这件事做得更为彻底:shared_ptrm_a; shared_ptrm_b;这样,我们就可以轻而易举地写出异常安全的代码:
    Type() : m_a(new TypeA), m_b(new TypeB){}
      如果你觉得shared_ptr的性能不能满足要求,可以编写一个接口类似scoped_ptr的智能指针类,在析构函数中释放资源即可。如果类设计成不可复制的,也可以直接用scoped_ptr。强烈建议不要把auto_ptr作为数据成员使用,scoped_ptr虽然名字不大好,但是至少很安全而且不会导致混乱。
      RAII技术并不仅仅用于上述例子中,所有必须成对出现的操作都可以通过这一技术完成而不必try...catch.下面的代码也是常见的:
    a_lock.lock();
    try{ ...} catch(...) {a_lock.unlock();throw;}
    a_lock.unlock();

      可以这样解决,先提供一个成对操作的辅助类:
    struct scoped_lock{
     explicit scoped_lock(Lock& lock) : m_l(lock){m_l.lock();}
     ~scoped_lock(){m_l.unlock();}
     private:
      Lock& m_l;
    };

      然后,代码只需这样写:
    scoped_lock guard(a_lock);
    do_something...

      清晰而优雅!继续考察这个例子,假设我们并不需要成对操作, 显然,修改scoped_lock构造函数即可解决问题。然而,往往方法名称和参数也不是那么固定的,怎么办?可以借助这样一个辅助类:
    template
    struct pair_guard{
     pair_guard(FEnd fe, FBegin fb) : m_fe(fe) {if (fb) fb();}
     ~pair_guard(){m_fe();}
     private:
      FEnd m_fe;
      ...//禁止复制
    };
    typedef pair_guard, function>simple_pair_guard;

      好了,借助boost库,我们可以这样来编写代码了:
    simple_pair_guard guard(bind(&Lock::unlock, a_lock), bind(&Lock::lock, a_lock) );
    do_something...

      我承认,这样的代码不如前面的简洁和容易理解,但是它更灵活,无论函数名称是什么,都可以拿来结对。我们可以加强对bind的运用,结合占位符和reference_wrapper,就可以处理函数参数、动态绑定变量。所有我们在catch内外的相同工作,交给pair_guard去完成即可。
    考察前面的几个例子,也许你已经发现了,所谓异常安全的代码,竟然就是如何避免try...catch的代码,这和直觉似乎是违背的。有些时候,事情就是如此违背直觉。异常是无处不在的,当你不需要关心异常或者无法处理异常的时候,就应该避免捕获异常。除非你打算捕获所有异常,否则,请务必把未处理的异常再次抛出。try...catch的方式固然能够写出异常安全的代码,但是那样的代码无论是清晰性和效率都是难以忍受的,而这正是很多人抨击C++异常的理由。在C++的世界,就应该按照C++的法则来行事。
      如果按照上述的原则行事,能够实现基本保证了吗?诚恳地说,基础设施有了,但技巧上还不够,让我们继续分析不够的部分。

    C++异常处理机制核心观点

    0.如果使用普通的处理方式:ASSERT,return等已经足够简洁明了,请不要使用异常处理机制.
    1.比C的setjump,longjump优秀.
    2.可以处理任意类型的异常. 你可以人为地抛出任何类型的对象作为异常.
        throw 100;
        throw \"hello\";

    3.需要一定的开销,频繁执行的关键代码段避免使用 C++异常处理机制.
    4.其强大的能力表现在:
    A.把可能出现异常的代码和异常处理代码隔离开,结构更清晰.
    B.把内层错误的处理直接转移到适当的外层来处理,化简了处理流程.传统的手段是通过一层层返回错误码把错误处理转移到上层,上层再转移到上上层,当层数过多时将需要非常多的判断, 以采取适当的策略.
    C.局部出现异常时,在执行处理代码之前,会执行堆栈回退,即为所有局部对象调用析构函数,保证局部对象行为良好.
    D.可以在出现异常时保证不产生内存泄漏.通过适当的try,catch布局,可以保证delete pobj;一定被执行.
    E.在出现异常时,能够获取异常的信息,指出异常原因.并可以给用户优雅的提示.
    F.可以在处理块中尝试错误恢复.保证程序几乎不会崩溃. 通过适当处理,即使出现除0异常,内存访问违例,也能让程序不崩溃,继续运行,这种能力在某些情况下及其重要.
    以上ABCDEF可以使你的程序更稳固,健壮,不过有时让程序崩溃似乎更容易找到原因,程序老是不崩溃,如果处理结果有问题,有时很难查找. 5.并不是只适合于处理’灾难性的’事件.普通的错误处理也可以用异常机制来处理,不过如果将此滥用的话,可能造成程序结构混乱,因为异常处理机制本质上是程序处理流程的转移,不恰当的,过度的转移显然将造成混乱.许多人认为应该只在’灾难性的’事件上使用异常处理,以避免异常处理机制本身带来的开销,你可以认为这句话通常是对的.
    6.先让程序更脆弱,再让程序更坚强.首先,它使程序非常脆弱,稍有差错,马上执行流程跳转掉,去寻找相应的处理代码,以求适当的解决方式.很像一个人身上带着许多药品,防护工具出行,稍有头晕,马上拿出清凉油;遇到蚊子立刻拿出电蚊拍灭之.
    WINDOWS:

    7.将结构化异常处理结合/转换到C++异常对象,可以更好地处理WINDOWS程序出现的异常.
    8.尽一切可能使用try,catch,而不是win32本身的结构化异常处理或者MFC中的TRY,CATCH宏.

    用得恰到好处,方显C++异常之美妙!catch(…)
    {
    }
       这三个点并不是说要省略什么.相反,你需要在程序中实际输入这三个点.这是一个很好的默认CATCH块,应该把它放在其他所有CATCH块之后.

        1.7异常规范:
        Double safe_divide(int top,int bottom) throw(DivideByZero);
        假如在函数中抛出一个异常,但异常规范中并未列出这个异常(也没有在函数内部捕捉),会发生什么事情?在这种情况下,程序会终止.尤其要注意的是,假如一个异常在函数中招聘但既没有在异常规范中列出,也没有在函数内部捕捉,那么它不会被任何catch块捕捉,而是直接导致程序终止.记住,如果完全没有异常规范列表,就连空白的都没有,那么效果等同于在规范列表中列出所有异常.在这种情况下,抛出一个异常不会终止程序.
    注意,异常规范是为那些准备跑到函数外部的异常而准备的.如果它们不跑到函数外部,就不归入异常规范.如果它们要跑到函数外部,就应该归入异常规范,无论它们起源于何处.如果在函数定义内部的一个try块中抛出一个异常,而且在函数定义内部的一个catch块中捕捉这个异常这个异常的类型就不需要在异常规范中列出.如果函数定义包括对另一个函数的调用,而另一个函数可能招聘一个它自已不会被捕捉的异常就应该在异常规范中列出异常的类型.
        要表示一个函数不应抛出任何不在函数内部捕捉的异常,需要使用一个空白异常规范.
        Void some_function() throw();
        几种方式可以总结如下:
        Void some_funtion() throw(DivideByZero,OtherExecption);
        //DivideByZero或OtherException类型的异常会被正常处理.
        //至于其他任何异常,如果抛出后未在函数主体中捕捉,就会终止程序.
        Void some_function()throw();
        //空异常列表:一旦抛出任何未在函数主体中捕捉的异常就会终止程序.
        Void some_function();
        //正常处理所有类型的所有异常.

        1.8陷阱:派生类中的异常规范
        在派生类中重定义或覆盖一个函数定义时,它应具有与基类中一亲友的异常规范,或至少应该在新的异常规范中给出基类异常规范的一个子集.换言之,重定义或覆盖一个函数定义时,不可在异常规范中添加新异常.但是,如果愿意,可删减基类中原有的异常.之所以有这个要求,是因为在能够使用基类对象的任何地方,都能使用一个派生类对象.因此,重定义或覆盖的函数必须兼容于为基类对象编写的任何代码.
        下面的内容是我从<<C++面向对象程序设计(第五版)>>找到值得学习的内容assert语句我们曾用以下语句测试一个名为in_stream的文件是否成功打开:
    If(in_stream.fail())
    {
        Cout<<”input file opening failed.\\n”;
        Exit(1);
    }
        可用assert(断言)语句编写同样的测试,如下所示:assert(!in_stream.fail());
    注意,这种情况下,我们要插入一个求反操作符(!),才能获得同样的效果,因为我们要断言的是文件打开操作”没有失败”.
        Assert语句由标识符assert,一个逻辑表达式(包含在一对圆括号内)各一个分号构成.可以使用任何逻辑表达式.如果逻辑表达式false,程序就终止运行,并给出一个错误消息.如果逻辑表达式示值为true,就什么情况都不会发生,程序继续执行assert语句之后的下一条语句.因此,assert 语句是在程序中进行错误检查的一种精简方式.
    Assert语句在cassert库中定义,所以使用assert语句的任何程序都必须包含以下
    include预编译指令:
        #include<cassert>
        Assert是一个宏(类似于函数的一种结构),所以有必要在一个库中定义它.
    使用assert语句的一个好处是可以将其关闭.你可在自己的程序中用assert语句来高度程序,再将其关闭使用户看不到他们无法理解的错误消息.关闭assert语句,还能减少程序执行这些语句的开销.要关闭程序中的所有assert语句,请在include预编译指令之前添加#define NDEBUG,如下所示:
        #define NDEBUG
        #include<cassert>
        因此,如果在进行了全面高度的程序中插入#define NDEBUG,就会关闭程序中的所有assert语句.如果以后改动了程序,可删除程序中的#define NDEBUG重新打开assert语句第6点补充为:
        6.先让程序更脆弱,再让程序更坚强.首先,它使程序非常脆弱,稍有差错,马上执行流程跳转掉,去寻找相应的处理代码,以求适当的解决方式,如果找不到任何解决办法(异常没有被捕获并处理),立刻(结束程序运行)。
        很像一个人身上带着许多药品,防护工具出行,稍有头晕,马上拿出清凉油;遇到蚊子立刻拿出电蚊拍灭之,遇到哪怕是蚊虫叮咬,如果找不到电蚊拍,他马上自杀!(这个家伙真是脆弱:) 以致于我们必须为他准备所有的防护工具一旦我们为他准备了全部适当的防护工具,他就变成一个非常坚强的人了!)。
        当然,代价是这个家伙变得比较笨重,行动迟缓。

    近似的公理:
    1 异常是同步的。即只能发生在函数边界。预定义类型的算术操作、预定义类型的不会导致异常
    2 对象的销毁是异常安全的。即析构函数、operator delete 和operator delete[] 是异常安全的。
    3 swap函数不会导致异常

    使用dbghelp获取调用堆栈--release下的调试方法学

    Author : Kevin Lynx

    当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因。常见的手法是输出LOG文件,根据LOG文件分析
    程序崩溃时的运行情况。我们可以通过SEH来捕获程序错误,然后输出一些有用的信息作为我们分析错误的资料。一般我们需要
    输出的信息包括:系统信息、CPU寄存器信息、堆栈信息、调用堆栈等。而调用堆栈则是最有用的部分,它可以直接帮我们定位
    到程序崩溃时所处的位置(在何处崩溃)。(codeproject上关于这个专题的常见开场白 = =#)

    要获取call stack(所谓的调用堆栈),就需要查看(unwind)stack的内容。We could conceivably attempt to unwind the
    stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler
    optimizations and calling conventions, so it could become complicated to do it that way.(摘自vld文档)要获取栈的
    内容,我们可以自己使用内联汇编获取,但是考虑到兼容性,内联汇编并不是一个好的解决方案。我们可以使用微软的dbghelp
    中的StackWalk64来获取栈的内容。

    StackWalk64声明如下:
    BOOL StackWalk64(
      DWORD MachineType,
      HANDLE hProcess,
      HANDLE hThread,
      LPSTACKFRAME64 StackFrame,
      PVOID ContextRecord,
      PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
      PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
      PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
      PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
    );

    具体每个参数的含义可以参见MSDN。这里说下ContextRecord参数,该参数指定了CPU各个寄存器的内容。StackFrame指定了stack frame的内容。stack frame是什么,我也不知道。(= =) StackWalk64函数需要用户指定当前frame的地址,以及当前程序的指令地址。这两个信息都被填充进ContextRecord,然后传进StackWalk64函数。

    那么如何获取当前的stack frame地址和当前程序指令地址呢?如前所说,你可以使用内联汇编。(对于程序指令地址,因为要获取EIP寄存器的内容,而该寄存器不能被软件访问)也可以使用GetThreadContext一次性获取当前线程当前运行情况下的CPU各个寄存器内容。补充下,当前frame地址被放在EBP寄存器里,当前程序指令地址放在EIP寄存器里。但是,如同MSDN对GetThreadContext函数的说明一样,该函数可能获取到错误的寄存器内容(You cannot get a valid context for a running thread)。

    另一种获取Context(包含EBP and EIP)的方法就是使用SEH(结构化异常处理),在__except中使用GetExceptionInformation获取。 GetExceptionInformation 传回一个LPEXCEPTION_POINTERS指针,该指针指向一个EXCEPTION_POINTERS结构,该结构里包含一个Context的指针,即达到目标,可以使用StackWalk函数。

    补充一下,你可以直接使用StackWalk函数,StackWalk被define为StackWalk64(windows平台相关)。 unwind栈后,可以进一步获取一个stack frame的内容,例如函数名。这里涉及到SymFromAddr函数,该函数可以根据一个地址返回符号名(函数名)。还有一个有意思的函数:SymGetLineFromAddr,可以获取函数对应的源代码的文件名和行号。

    当然,这一切都依赖于VC产生的程序数据库文件(pdb),以及提供以上API函数的dbghelp.dll。

    参考一段简单的代码:

    #include <windows.h>
    #include <stdio.h>
    #include <dbghelp.h>

    #pragma comment( lib, "dbghelp.lib" )

    void dump_callstack( CONTEXT *context )
    {
        STACKFRAME sf;
        memset( &sf, 0, sizeof( STACKFRAME ) );

        sf.AddrPC.Offset = context->Eip;
        sf.AddrPC.Mode = AddrModeFlat;
        sf.AddrStack.Offset = context->Esp;
        sf.AddrStack.Mode = AddrModeFlat;
        sf.AddrFrame.Offset = context->Ebp;
        sf.AddrFrame.Mode = AddrModeFlat;

        DWORD machineType = IMAGE_FILE_MACHINE_I386;

        HANDLE hProcess = GetCurrentProcess();
        HANDLE hThread = GetCurrentThread();

        for( ; ; )
        {
            if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
            {
                break;
            }

            if( sf.AddrFrame.Offset == 0 )
            {
                break;
            }
            BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
            PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;

            pSymbol->SizeOfStruct = sizeof( symbolBuffer );
            pSymbol->MaxNameLen = 1024;

            DWORD64 symDisplacement = 0;
            if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
            {
                printf( "Function : %s\n", pSymbol->Name );
            }
            else
            {
                printf( "SymFromAdd failed!\n" );
            }

            IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
            DWORD dwLineDisplacement;

            if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
            {
                printf( "[Source File : %s]\n", lineInfo.FileName );
                printf( "[Source Line : %u]\n", lineInfo.LineNumber );
            }
            else
            {
                printf( "SymGetLineFromAddr failed!\n" );
            }
        }
    }

    DWORD excep_filter( LPEXCEPTION_POINTERS lpEP )
    {
        /**//// init dbghelp.dll
        if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
        {
            printf( "Init dbghelp ok.\n" );
        }

        dump_callstack( lpEP->ContextRecord );

        if( SymCleanup( GetCurrentProcess() ) )
        {
            printf( "Cleanup dbghelp ok.\n" );
        }

        return EXCEPTION_EXECUTE_HANDLER;
    }

    void func1( int i )
    {
        int *p = 0;
        *p = i;
    }

    void func2( int i )
    {
        func1( i - 1 );
    }

    void func3( int i )
    {
        func2( i - 1 );
    }

    void test( int i )
    {
        func3( i - 1 );
    }

    int main()
    {
        __try
        {
            test( 10 );
        }
        __except( excep_filter( GetExceptionInformation() ) )
        {
            printf( "Some exception occures.\n" );
        }

        return 0;
    }

    以上代码在release模式下需要关掉优化,否则调用堆栈显示不正确(某些函数被去掉了?),同时需要pdb文件。当用户使用时可以只抛出挂掉时候的地址,然后开发者通过crashfinder这样的软件,启动拥有pdb的程序

    April 21

    dll 中 string 内存泄漏问题

    dll 导出函数即使间接引用的函数,如果输入参数为std::string 引用类型,那么该string 会引起内存泄漏。
    如导出函数,这种容易发现。但间接的很难查找。: 
    void dllex(char* s)
    {
       std::string test = s
    }
    但不一定就泄漏,很光棍的问题。类A中一个类成员变量B的成员name为string 。赋值时出现内存泄漏.而我在A中声明一个string成员同样取得相同值。缺没有内存泄漏出现。郁闷,百思不得其解中,弄错了这里
    void dllex(string& str)  如果dllex 中修改了str内容,这样会有内存泄漏。
    终于找到原因了,回收b的地方memset了结构对象放入了回收列表。而原来是用char数组的,改成string 实现没有发现这个地方,
    :( ,发现内存泄漏后,反倒一直在alloc的地方找问题。郁闷

    April 18

    错误c2059

    疯了,浪费了一个小时。删除掉了一个空行就出了这个问题。加回去正常编译。日
    怀疑预编译头文件有问题,把当前文件预编译选项设成了不使用预编译头,编译又过了。
    等我加了个空行,日啊,又不行了。
    最后发现是 typedef struct {} structa; 这个结构中我放入了一个string 类型,这可捅了马蜂窝,浪费时间和心情。
    typedef struct
    {

    } name;
    这是常用的 c 中的写法。
    鄙视. 郁闷, 总夸c++ 灵活,灵活的地方小人物用不到,我是没信心去搞模板那一套,搞得比perl还难懂。用常用的东西,还要躲避乱七八糟的规则,烦啊

    得到的教训是:struct 只包含原型成员不应包含类成员(struct 定义的类也算),可以作为编码规范。如果需要类成员,则定义为类class 而非struct。还有认清typedef的用法

    在使用2003时遇到的问题,到2005似乎没有了。忘记了当时的情形。 :(, 不过现在用2005不用为这个烦恼了。typedef 还是可以用的

    March 25

    VC的预编译功能

    这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考: MSDN -> Visual Studio 6.0 Document -> Visual C++ 6.0 Document -> VC++ Programmer Guider ->Compiler and Linker -> Details -> Creating Precompiled Header files
       预编译头的概念:
       所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头文件。
       也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。 VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。
       根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。
       要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:
    #include <afxwin.h> // MFC core and standard components
    #include <afxext.h> // MFC extensions
    #include <afxdisp.h> // MFC Automation classes
    #include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
    #include <afxcmn.h>
       这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
       那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include“Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。
    在vc文件列表中右键单击 stdafx.cpp->属性->c/c++ ->预编译头->创建使用预编译头 选择创建预编译头(/yc)
    对工程设置使用预编译头。在工程名上右击 属性->c/c++ ->预编译头->创建使用预编译头 选择使用预编译头(/yu)
    对于不需要预编译头的源文件,可以像设置stdafx.cpp属性一样。在其属性中选择不使用预编译头即可.
    默认的预编译头名字是stdafx.h.当然你也可以修改成其他名字.
    使用了预编译头的工程在其包含的源文件中开头必须 #include "stdafx.h"  注意即使不在同一目录也不需要指定相对路径.如:
    a.cpp 在common 目录 stadfx.h 在main 目录,不需要#include "../main/stdafx.h" 这样是错误的。

    在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
    这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
       1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end.
       2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。

    转载并补充了一点东西,使用预编译头之后,尽量在头文件中不包含其他头文件,可以提高编译速度。原来10几秒编译的工程现在都是秒过了。hoho

    February 21

    stlport 标准库内存泄漏

    使用stlport vector 居然发现了内存泄漏.
    STLport现在的做法是会特意泄漏内存的,如果你检测到内存泄漏,经过讨论,STLport采用了特意不回收收集的小块内存,而借助现代操作系统的自动回收能力(应用程序退出时会收回分配给进程的内存)的做法,以减少内存印记、获得较好的再分配能力和性能。具体的详细解释,可以读源码,上STLport的交流论坛,或者看《STL源码剖析》——里面有关于SGI内存适配器(也是STLport所采用的)的解析。
    尽管泄漏一次不算什么内存泄漏,但当程序过大时候,可能内存泄漏数据多了起来会影响调试.也可以避免这种情况如 含有 int 的 vector  resize 到 64 即可避免因小块内存而出现的内存泄漏,内存泄漏的一些调试方法可以使用vld 也可以用ms的调试函数. 可以参考前面的使用VLD检测内存泄露:
     http://topameng.spaces.live.com/blog/cns!F962D4854A8233D!341.entry
    November 01

    使用VLD检测内存泄露

    1 要能够配置vld 即可以使用下面的宏。由于我的是vc2003 。而网上的是vc6和7编译出来的库,不知道是什么版本(可能是多线程调试,单线程调试之类设置不匹配),这些配置宏不起作用. 结果:运行结束后,在调试窗口打印所有泄漏内存中的数据,结束程序时间超长。
    需要设置 VLD_MAX_DATA_DUMP 宏来控制打印数据的多少, 所以必须用源码编译一个。

    VLD_AGGREGATE_DUPLICATES
    重复内存泄露在同一个地方,只显示一次堆栈信息。但包含泄露次数
    VLD_MAX_TRACE_FRAMES
    跟踪的堆栈数目 #define VLD_MAX_TRACE_FRAMES 9 过少的话会发现没有调用函数堆栈显示。9 能显示到 new 的位置
    VLD_MAX_DATA_DUMP
    打印泄露内存数据长度。有些地方泄露内存数据太大,如果全部打印,退出程序时间超长。
    #define VLD_MAX_DATA_DUMP 10

    VLD_SELF_TEST
    测试自己

    VLD_SHOW_USELESS_FRAMES
     

    VLD_START_DISABLED

    2 编译vld 需要配置vc include 路径,加入vc crt目录,例如:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src (最好放到所有include目录最后面)

    3 要注意不要把工程放入到中文目录,这样就不能看到泄露的函数堆栈了。那样还不如:
    #include "crtdbg.h"
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);

    我在 dll 中使用vld 。当动态调用loadlibrary 来使用dll 时候。vld 不能报告内存泄漏
    使用CrtSetDbgFlag 会在vc output 窗口中报告第多少块内存分配有泄漏然后使用CrtSetBreakAlloc中断即可。这两个函数放入dllmain中即可中断到泄漏的分配地点

    调试窗口内容:
    Dumping objects ->
    {353} normal block at 0x003B9C58, 320 bytes long.
    ~~~ 第353次内存分配发生内存泄漏
     Data: <  ;             > F0 9C 3B 00 CD CD CD CD 00 00 00 00 CD CD CD CD
     _CrtSetBreakAlloc(353);
    BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD reason, LPVOID)
    {
     //可以中断在第353次分配内存的地方
     _CrtSetBreakAlloc(353); 
     _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
     _tsetlocale(LC_ALL,_T("chs"));
     return TRUE;
    }
    September 11

    C语言中可变参数的用法(转载)


    C语言中可变参数的用法
    我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() 这个函数,它的定义是这样的:
    int printf( const char* format, ...);
    它除了有一个参数format固定以外,后面跟的参数的个数和类型是 可变的,例如我们可以有以下不同的调用方法:
    printf("%d",i);
    printf("%s",s);
    printf("the number is %d ,string is:%s", i, s);
    究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现的呢?本文就这个问题进行一些探讨,希望能对大家有些帮助.会C++的网友知道这些问题在C++里不存在,因为C++具有多态性.但C++是C的一个超集,以下的技术也可以用于C++的程序中.限于本人的水平,文中如果有不当之处,请大家指正.
    (一)写一个简单的可变参数的C函数
    下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的C函数要在程序中用到以下这些宏:
    void va_start( va_list arg_ptr, prev_param );
    type va_arg( va_list arg_ptr, type );
    void va_end( va_list arg_ptr );
    va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个
    头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.
    void simple_va_fun(int i, ...)
    {
    va_list arg_ptr;
    int j=0;
    va_start(arg_ptr, i);
    j=va_arg(arg_ptr, int);
    va_end(arg_ptr);
    printf("%d %d\n", i, j);
    return;
    }
    我们可以在我们的头文件中这样声明我们的函数:
    extern void simple_va_fun(int i, ...);
    我们在程序中可以这样调用:
    simple_va_fun(100);
    simple_va_fun(100,200);
    从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
    1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
    2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
    3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
    4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.
    如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:
    1)simple_va_fun(100);
    结果是:100 -123456789(会变的值)
    2)simple_va_fun(100,200);
    结果是:100 200
    3)simple_va_fun(100,200,300);
    结果是:100 200
    我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果的原因和可变参数在编译器中是如何处理的.
    (二)可变参数在编译器中的处理
    我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下(’\’号表示折行):
    typedef char * va_list;
    #define _INTSIZEOF(n) \
    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
    #define va_arg(ap,t) \
    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define va_end(ap) ( ap = (va_list)0 )
    定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆
    栈的地址,如图:
    高地址|-----------------------------|
    |函数返回地址 |
    |-----------------------------|
    |....... |
    |-----------------------------|
    |第n个参数(第一个可变参数) |
    |-----------------------------|<--va_start后ap指向
    |第n-1个参数(最后一个固定参数)|
    低地址|-----------------------------|<-- &v
    图( 1 )
    然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值:
    j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
    首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.
    高地址|-----------------------------|
    |函数返回地址 |
    |-----------------------------|
    |....... |
    |-----------------------------|<--va_arg后ap指向
    |第n个参数(第一个可变参数) |
    |-----------------------------|<--va_start后ap指向
    |第n-1个参数(最后一个固定参数)|
    低地址|-----------------------------|<-- &v
    图( 2 )
    最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.关于va_start, va_arg, va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
    (三)可变参数在编程中要注意的问题
    因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数
    printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 另外有一个问题,因为编译器对可变参数的函数的原型检查不够严
    格,对编程查错不利.如果simple_va_fun()改为:
    void simple_va_fun(int i, ...)
    {
    va_list arg_ptr;
    char *s=NULL;
    va_start(arg_ptr, i);
    s=va_arg(arg_ptr, char*);
    va_end(arg_ptr);
    printf("%d %s\n", i, s);
    return;
    }
    可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出错,但错误却是难以发现,不利于我们写出高质量的程序.
    以下提一下va系列宏的兼容性.
    System V Unix把va_start定义为只有一个参数的宏:
    va_start(va_list arg_ptr);
    而ANSI C则定义为:
    va_start(va_list arg_ptr, prev_param);
    如果我们要用system V的定义,应该用vararg.h头文件中所定义的宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以用ANSI C的定义就够了,也便于程序的移植.
    小结:
    可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现. 写这篇文章时,适逢感冒,多得有她的关怀,谨把这篇文章献给她...
    原作者: kevintz
    junglesong 转载自www.chiangpa.com



    补充说明:

    #include <cstdlib>
    #include <iostream>
    #include <stdarg.h>

    using namespace std;

    int getarg(int* arg)
    {
        printf("name is :%d\n",arg[0]);
        printf("name is :%s\n",(char *)arg[1]);
        printf("name is :%d\n",arg[2]);
        printf("char is :%c\n",arg[3]);
        printf("char is :%c\n",arg[4]);

        return 0;
    }

    int _cdecl testarg(int t,char* name,...)
    {

        getarg(&t);
        return 0;
    }

    int main(int argc, char *argv[])
    {
        testarg(11,"ameng",1000,'c','d');
        system("PAUSE");
        return 0;
    }


    在getarg()函数堆栈类似下面这样:

    sp+32    parm1
    sp+28    parm0
    sp+24    return value
    sp+20    return address
    sp+16    local1
    sp+14    local0
    sp+12    arg1
    sp+8     arg0
    sp+4     return stack
    sp        return address

    因为arg 是testarg 函数的第一个参数地址,所以 arg + 4 便是testarg后继参数中的第一个参数name的地址。依次类推,这样就不需要使用va_list来获得参数了

    September 05

    c语言读取bmp文件格式

    //BMP 头
    typedef struct
    {
        char id[2];                                   //bmp 文件标志 "BM"
        unsigned long fileSize;                    //文件大小
        unsigned long reserved0;
        unsigned long bitmapDataOffset;
        unsigned long bitmapHeaderSize;
        unsigned long width;                    //图像宽度
        unsigned long height;                   //图像高度
        unsigned short planes;
        unsigned short bitsPerPixel;            //每个像素站多少位
        unsigned long compression;            //是否压缩
        unsigned long bitmapDataSize;
        unsigned long hRes;
        unsigned long vRes;
        unsigned long colors;
        unsigned long importantColors;
        unsigned char palette[256][4];      //调色板数据,24位及以上像素没有该数据
    } BMPHeader_t;

    long GetFileSize(FILE* f)
    {
        long pos = ftell(f);
        fseek(f,0,SEEK_END);
        long len = ftell(f);
        fseek(f,pos,SEEK_SET);
        return len;
    }

    void __stdcall Com_Error(const char *fmt, ... )
    {
        char    com_errorMessage[4096];
        va_list        argptr;

        va_start (argptr,fmt);
        vsprintf (com_errorMessage,fmt,argptr);
        va_end (argptr);

        MessageBox(NULL,com_errorMessage,"错误提示",MB_ICONERROR);
    }

    //*pic 返回 RGBA 类型像素数据,width 返回修正后的宽度,height返回高度
    // name 输入的文件名称
    bool LoadBMP( const char *name, byte **pic, int *width, int *height )
    {
        int        columns, rows, numPixels;
        byte    *pixbuf;
        int        row, column;
        byte    *buf_p;
        byte    *buffer;
        int        length;
        BMPHeader_t bmpHeader;
        byte        *bmpRGBA;
        *pic = NULL;
        // load the file
        FILE* pfile = fopen(name,"rb");
        if(pfile == NULL)
        {
            Com_Error( "LoadBMP: Load BMP files failed (%s)\n", name );
            return false;
        }
        length = GetFileSize(pfile);
        buffer = (byte*) malloc(length*sizeof(byte));
        fread(buffer,1,length,pfile);
        if (!buffer)
        {
            Com_Error( "LoadBMP: Memory alloc failed (%s)\n", name );
            return false;
        }

        buf_p = buffer;

        bmpHeader.id[0] = *buf_p++;
        bmpHeader.id[1] = *buf_p++;
        bmpHeader.fileSize = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.reserved0 = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.bitmapDataOffset = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.bitmapHeaderSize = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.width = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.height = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.planes = ( * ( short * ) buf_p );
        buf_p += 2;
        bmpHeader.bitsPerPixel = ( * ( short * ) buf_p );
        buf_p += 2;
        bmpHeader.compression = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.bitmapDataSize = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.hRes = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.vRes = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.colors = ( * ( long * ) buf_p );
        buf_p += 4;
        bmpHeader.importantColors = ( * ( long * ) buf_p );
        buf_p += 4;

        memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );

        if ( bmpHeader.bitsPerPixel == 8 )
            buf_p += 1024;

        if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' )
        {
            Com_Error( "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
            return false;
        }
        if ( bmpHeader.fileSize != length )
        {
            Com_Error("LoadBMP: header size does not match file size (%d vs. %d) (%s)\n", bmpHeader.fileSize, length, name );
            return false;
        }
        if ( bmpHeader.compression != 0 )
        {
            Com_Error("LoadBMP: only uncompressed BMP files supported (%s)\n", name );
            return false;
        }
        if ( bmpHeader.bitsPerPixel < 8 )
        {
            Com_Error("LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
            return false;
        }

        columns = bmpHeader.width;
        rows = bmpHeader.height;

        if(bmpHeader.bitsPerPixel == 24 )
        {
            if( (columns & 3) != 0)                    //检查宽度是否为4倍数
               columns = (columns & ~3) + 4;    //修正位图宽度值,对齐到4的倍数,不然图像会变形  
        }

        if ( rows < 0 )
            rows = -rows;
        numPixels = columns * rows;

        if ( width )
            *width = columns;
        if ( height )
            *height = rows;

        bmpRGBA = (byte*)malloc( numPixels * 4 );
        *pic = bmpRGBA;
        buf_p = buffer+54;

        for ( row = rows-1; row >= 0; row--)
        {
            pixbuf = bmpRGBA + row*columns*4;

            for ( column = 0; column < columns; column++ )
            {
                unsigned char red, green, blue, alpha;
                int palIndex;
                unsigned short shortPixel;

                switch ( bmpHeader.bitsPerPixel )
                {
                case 8:
                    palIndex = *buf_p++;
                    *pixbuf++ = bmpHeader.palette[palIndex][2];
                    *pixbuf++ = bmpHeader.palette[palIndex][1];
                    *pixbuf++ = bmpHeader.palette[palIndex][0];
                    *pixbuf++ = 0xff;
                    break;
                case 16:
                    shortPixel = * ( unsigned short * ) pixbuf;
                    pixbuf += 2;
                    *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
                    *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
                    *pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
                    *pixbuf++ = 0xff;
                    break;

                case 24:
                    blue = *buf_p++;
                    green = *buf_p++;
                    red = *buf_p++;
                    *pixbuf++ = red;
                    *pixbuf++ = green;
                    *pixbuf++ = blue;
                    *pixbuf++ = 255;
                    break;
                case 32:
                    blue = *buf_p++;
                    green = *buf_p++;
                    red = *buf_p++;
                    alpha = *buf_p++;
                    *pixbuf++ = red;
                    *pixbuf++ = green;
                    *pixbuf++ = blue;
                    *pixbuf++ = alpha;
                    break;
                default:
                    Com_Error("LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name );
                    return false;
                }
            }
        }

        free(buffer);
        fclose(pfile);
        return true;
    }
    //注意bmp文件是由左到右, 由下到上的循序存储的像素.每个像素颜色顺序为B G R (A)
    //BMP的行象素是4字节对齐的,不足的补0,比如有个图像每行宽度是63象素,
    //BMP文件存储时会每行存储64个字节,最后一个字节用0补齐

    void CDlg::OnPaint()   //测试图像
    {
    CPaintDC dc(this);
    static byte* pic = NULL;
    static int w,h;
    if(pic == NULL)
    {
        char spath[MAX_PATH];
        _getcwd(spath, 200);
        strcat(spath,"\\res\\b0000.bmp");
        LoadBMP(spath,&pic,&w,&h);
    }
    int i,j;
    for(j=0;j<h;j++)
    {
        for(i=0;i<w;i++)
        {            
            SetPixel(dc,i,j,RGB(pic[0],pic[1],pic[2]));
            pic += 4;
        }
    }
    }

    July 19

    堆栈丢失的错误 00000000()

      程序报错,中断后堆栈中只有00000000().注意最后的()在vc里很容易被看成0. 造成这种错误的一种情况是:
     1. 调用结构中的成员,此成员为函数指针。但指针没有赋值或者值为NULL。而当调用这个函数的时候会跳出这样的错误情况
     2. 同样在OnTimer 定时器函数中调用一个指向NULL的函数指针也会发生这种错误
     3. 声明了函数指针,但未给其赋值。通过extern 外部引用了这个指针函数。当调用函数时也会出现这种错误

    vc 优化与内存对齐

      终于看到了quake3 的Renderer项目中的代码,发现了这样一段:
    if ( (int)tess.xyz & 15 )   
    {
              Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" );
    }
      xyz 是一个矢量(float [4]) 数组。
       当我自己的工程 Release 编译时,进入了这个警告。这个警告的意思是 tess的结构成员 xyz 所在的内存地址不是16byte对齐的。而对于quake3 的工程编译后却没有进入这个警告。直觉告诉我肯定是一些vc工程的设置问题,经过层层筛选,最后终于定位到。默认的vc release 工程,链接器-〉优化-〉引用:选择的是/OPT:REF,这个可以优化掉不用的函数减少文件大小。但Renderer 项目作为quake3 项目的依赖项,这样作用并不大。相反这个优化反而导致了内存变化。对于游戏程序来说,矢量对齐到16byte可以提高一定游戏性能。尽管可以在xyz 的声明前加上__declspec (align(16)),强制这个变量内存对齐到16byte边界。但需要修改的地方实在太多。
      如果你的程序不需要计较多几K大小,这个选项还是选为默认吧。即不对其进行优化.
      注意:由于quake3变量顺序安排的巧妙,才使tess.xyz 对齐到16位内存上的。如果改变了变量定义顺序,或者打开了上面的优化。都会导致tess.xyz不能对齐到16byte.如果自己不愿去计算,还是使用__declspec (align(16)) 吧

        刚把所有blog 整理个列表,ms 就更新了。美其名曰:摘要,一页能浏览20个选项。真是。。。
    July 14

    巧用回收的指针

    指针的指针总是容易让人困惑,更能带给你惊奇.
    typedef struct nodetype
    {
        struct    nodetype **head;  
    } node_t;       

    typedef struct
    {
        node_t**    freelist;              
        node_t*      nodePtrs[768];         
    } huff_t;

    static void free_ppnode(huff_t* huff, node_t **ppnode)
    {
        *ppnode = (node_t *)huff->freelist;        //让 ppnode 所在的节点存放 freelist 内存地址
        huff->freelist = ppnode;                        //freelist 指向第一个节点
    }

    //获取一个自由的node_t* 节点,用来存储 head (指向一个块中具有最大节点编号的节点)
    static node_t **get_ppnode(huff_t* huff)
    {
        node_t **tppnode;
        if (!huff->freelist)
        {
            return &(huff->nodePtrs[huff->blocPtrs++]);
        }
        else
        {
            tppnode = huff->freelist;                //freelist 指向的内存。*tppnode 中存贮着下一个自由节点地址
            huff->freelist = (node_t **)(*tppnode);    //freelist 节点指向下一个free node地址,注意强制转换。
            return tppnode;
        }
    }

     
      free_ppnode 每次回收ppnode的时候,在其指向的空间(nodePtrs[768] 中的元素)内容就不需要了,而这时修改ppnode指向空间中的数值(存放的是node_t * 变量,可以暂时认为其为32 位地址数字), 让其存放 freelist 变量值(可以暂时认为其是一个32位地址数字)。这样多次释放之后就形成了一个链表,但不需要任何其他的多余的元素协助。而普通链表中的需要 next 指针协助才能连接两个链表节点。
     当然需要从链表取得下一个元素时,需要对 freelist 进行提领操作来获得以前的freelist 变量值
    June 16

    搜索文件目录获取文件列表

    //dest 写入的目标,size 目标大小,写入的格式,写入的内容
    void Com_sprintf( char *dest, int size, const char *fmt, ...)
    {
     int  len;
     va_list  argptr;
     char bigbuffer[32000]; // big, but small enough to fit in PPC stack
     va_start (argptr,fmt);
     len = vsprintf (bigbuffer,fmt,argptr);
     va_end (argptr);
     if ( len >= sizeof( bigbuffer ) )
     {
      ASSERT("Com_sprintf: overflowed bigbuffer" );
     }
     if (len >= size)
     {
      ASSERT ("Com_sprintf: overflow");
     }
     strncpy( dest, bigbuffer, size-1 );
     dest[size-1] = 0;
    }
     
    //字符串比较,忽略大小写
    int Q_stricmpn (const char *s1, const char *s2, int n)          
    {
     int  c1, c2;
     do
     {
      c1 = *s1++;
      c2 = *s2++;
      if (!n--)
      {
       return 0;  // strings are equal until end point
      }
      if (c1 != c2)
      {
       if (c1 >= 'a' && c1 <= 'z')
       {
        c1 -= ('a' - 'A');
       }
       if (c2 >= 'a' && c2 <= 'z')
       {
        c2 -= ('a' - 'A');
       }
       if (c1 != c2)
       {
        return c1 < c2 ? -1 : 1;
       }
      }
     } while (c1);
     return 0;  // strings are equal
    }
    int Q_stricmp (const char *s1, const char *s2)
    {
     return Q_stricmpn (s1, s2, 99999);
    }
     
    //basedir 根目录,subdirs 子目录,存放文件的列表,列表大小
    //最开始选的目录为根目录,开始 subdirs 可设为 NULL
    int Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles )
    {
     char  search[MAX_OSPATH], newsubdirs[MAX_OSPATH];
     char  filename[MAX_OSPATH];
     intptr_t findhandle;
     struct _finddata_t findinfo;
     if ( *numfiles >= MAX_FOUND_FILES - 1 )
     {
      return -1;
     }
     if (strlen(subdirs))                                     
     {
      Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs );
      Com_sprintf( filename, sizeof(filename), "%s\\%s", basedir, subdirs );
     }
     else
     {
      Com_sprintf( search, sizeof(search), "%s\\*", basedir );                 
      //搜索的目录,* 表示搜多所有文件。*.mp3 即搜索mp3文件
      Com_sprintf( filename, sizeof(filename), "%s", basedir );
     }
     findhandle = _findfirst (search, &findinfo);
     if (findhandle == -1)                          
     {
      return 0;        //如果是*.mp3 之类,没有任何文件
     }
     do
     {
      if (findinfo.attrib & _A_SUBDIR) //是否为目录
      {
       if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, ".."))  //不是. 或者..
       {
        if (strlen(subdirs))
        {
         Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name);
        }
        else
        {
         Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name);
        }
        Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles );
       }  
      }
      else
      {
       if ( *numfiles >= MAX_FOUND_FILES - 1 )
       {
        return -1;
       }    
       list[*numfiles] = new char[MAX_PATH];
       Com_sprintf( list[*numfiles], MAX_PATH, "%s\\%s", filename,findinfo.name);      //存入文件名到列表中
       (*numfiles)++;
      }
     } while ( _findnext (findhandle, &findinfo) != -1 );
     _findclose (findhandle);
     return 0;
    }
     
     #define MAX_FOUND_FILES 0x1000       //处理不超过4096个文件。
     char* list[MAX_FOUND_FILES];
     int count = 0;
     if(Sys_ListFilteredFiles(str,"",NULL,list,&count))
     {
      MessageBox("文件过多,请重新选择更细的目录");
      for(int i=0;i<count;i++)
      { 
       delete list[i];
      }
     }
    May 17

    如何提高程序效率

    1 使用循环展开,对于循环内部只有一条语句可以采用循环展开来提升速度.
    2 对于2的倍数除法使用&运算,如 30 除 4 的余数为 30 & 3,30 类最大的4的倍数为30 & ~3.一个数是否为2的幂 n & 1. 而判断一个变量中含有多少个1可以
    3 对于一些只需要初始化一次的函数内部变量可以声明为static类型
    4 对于过大的switch语句块,可以采用表驱动法,对于switch如果条件过多,后面的条件分支必须等待前面判断完毕才能执行。而采用表驱动可以直接跳转到要执行的语句
    5  ^=  异或操作,切换某位状态由0到1,或者由1到0.   A ^= 0x00010 ,迅速切换第2位 . 这样比 true ,false 之类的切换要快。写法也简单
    6 ! 操作,如 strcmp 判断两个字符相等返回0,而不是 true ,而0值确对应 false. 和0比较相对于其他比较要快些。所以 if(!strcmp(a,b)),注意: ! 对于大于 0 的数返回 0,!0 怎返回 1
    7 ~= 判断1个dword 变量x有多少个位为1。 int c=0; for(;x~= x-1;c++); 最后c 为 x 含有1 的个数。
    熟练应用位操作有利于提高程序速度
     
    May 14

    c 不需要强制转换malloc的返回值

    C/C++ 误区三:强制转换 malloc() 的返回值

    来源:蚂蚁的 C/C++ 标准编程 作者:antigloss 等级:精品
    发布于2005-10-22 14:17 被读3109次 【字体:

        首先要说的是,使用 malloc 函数,请包含 stdlib.h(C++ 中是 cstdlib),而不是 malloc.h 。因为 malloc.h 从来没有在 C 或者 C++ 标准中出现过!因此并非所有编译器都有 malloc.h 这个头文件。但是所有的 C 编译器都应该有 stdlib.h 这个头文件。

        在 C++ 中,强制转换 malloc() 的返回值是必须的,否则不能通过编译。但是在 C 中,这种强制转换却是多余的,并且不利于代码维护。

        起初,C 没有 void 指针,那时 char* 被用来作为泛型指针(generic pointer),所以那时 malloc 的返回值是 char* 。因此,那时必须强制转换 malloc 的返回值。后来,ANSI C(即C89) 标准定义了void 指针作为新的泛型指针。void 指针可以不经转换,直接赋值给任何类型的指针(函数指针除外)。从此,malloc 的返回值变成了 void* ,再也不需要强制转换 malloc 的返回值了。以下程序在 VC6 编译无误通过。

            #include <stdlib.h>
            int main( void )
            {
                double *p = malloc( sizeof *p ); /* 不推荐用 sizeof( double ) */
               
    free(p);
                return 0;
            }

        当然,强制转换malloc的返回值并没有错,但画蛇添足!例如,日后你有可能把double *p改成int *p。这时,你就要把所有相关的 (double *) malloc (sizeof(double))改成(int *)malloc(sizeof(int))。如果改漏了,那么你的程序就存在 bug 。就算你有把握把所有相关的语句都改掉,但这种无聊乏味的工作你不会喜欢吧!不使用强制转换可以避免这样的问题,而且书写简便,何乐而不为呢?使用以下代码,无论以后指针改成什么类型,都不用作任何修改。

            double *p = malloc( sizeof *p );

        类似地,使用 calloc ,realloc 等返回值为 void* 的函数时,也不需要强制转换返回值。

    c++ 误区 fflush(stdin)

    来源:蚂蚁的 C/C++ 标准编程 作者:antigloss 等级:精品

    1.       为什么 fflush(stdin) 是错的

     

    首先请看以下程序:

     

                       #include <stdio.h>

     

    int main( void )

    {

        int i;

        for (;;) {

            fputs("Please input an integer: ", stdout);

            scanf("%d", &i);

            printf("%d\n", i);

        }

        return 0;

    }

     

    这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为 scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。

     

    也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);,把输入缓冲清空掉不就行了?”然而这是错的!CC++标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 不支持),因为标准中根本没有定义 fflush(stdin)MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standardfflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么大问题。以下是 C99 fflush 函数的定义:

     

    int fflush(FILE *stream);

     

    如果 stream 指向输出流或者更新流update stream),并且这个更新流
    最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至
    宿主环境(host environment)写入文件。否则,它的行为是未定义的。

    原文如下:


    int fflush(FILE *stream);

    If stream points to an output stream or an update stream in which
    the most recent
    operation was not input, the fflush function causes
    any unwritten data for that
    stream to be delivered to the host environment
    to be written to the file;
    otherwise, the behavior is undefined.

     

    其中,宿主环境可以理解为操作系统或内核等。

     

        由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin)  是不正确的,至少是移植性不好的。

     

     

    2.       清空输入缓冲区的方法

     

     虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。

            /* C 版本 */
            #include <stdio.h> 


            int main( void )
            {
                int i, c;
       
              
    for ( ; ; )
                {
                    fputs("Please input an integer: ", stdout);
                    scanf("%d", &i);

                 if ( feof(stdin) || ferror(stdin) )
                    {
    /* 如果用户输入文件结束标志(或文件已被读完), */
                      /* 或者发生读写错误,则退出循环               */

               
                        /* do something */
                        break;
                    }
                   
    /* 没有发生错误,清空输入流。                 */
                    /* 通过 while 循环把输入流中的余留数据“吃”掉 */
                    while ( (c = getchar()) != '\n' && c != EOF ) ;
                    /* 使用 scanf("%*[^\n]"); 也可以清空输入流, */

                   /* 不过会残留 \n 字符。                          */

                   printf("%d\n", i);
                }

                 return 0;
            }


            /* C++ 版本 */
            #include <iostream>
            #include <limits> // 为了使用numeric_limits

     

         using std::cout;
            using std::endl;
            using std::cin;
            using std::numeric_limits;
            using std::streamsize;

     

         int main()
            {
                int value;
                for ( ; ; )
                {
                    cout << "Enter an integer: ";
                    cin >> value;
                    if ( cin.eof() || cin.bad() )
                    { // 如果用户输入文件结束标志(或文件已被读完),
                      // 或者发生读写错误,则退出循环

                     // do something
                        break;
                    }
                    // 读到非法字符后,输入流将处于出错状态
                    // 为了继续获取输入,首先要调用 clear 函数
                    // 来清除输入流的错误标记,然后才能调用
                    // ignore 函数来清除输入流中的数据。
                    cin.clear();
                    // numeric_limits<streamsize>::max() 返回输入缓冲的大小。
                    // ignore 函数在此将把输入流中的数据清空。
                    // 这两个函数的具体用法请读者自行查询。

                    cin.ignore( numeric_limits<streamsize>::max(), '\n' );

                    cout << value << '\n';
                }

             return 0;
            }

    May 11

    使用c函数获取文件大小

    取得当前文件指针位置函数:
    long ftell(FILE *stream );
    如果文件指针位于文件尾,则取得了文件大小。
    int fseek(FILE *stream,long offset,int origin );
    stream:Pointer to FILE structure.
    offset:Number of bytes from origin.
    origin:Initial position. 可以取下面的值
    SEEK_CUR:Current position of file pointer.
    SEEK_END:End of file.
    SEEK_SET:Beginning of file.
    例如:取一个文件大小:
    //传入使用fopen打开的文件结构指针 File
      int currentPos = ftell(File); //取得当前文件指针位置,可能已经移动了文件指针
      fseek(File, 0, SEEK_END); //移动到文件的结尾
      int lastPos = ftell(File); //获得文件大小
      fseek(File,currentPos,SEEK_SET); //恢复到原来的文件指针位置
      return lastPos;
    如果文件大于 4G ,就要用64 位的 __int64 _ftelli64( FILE *stream );
    删除文件可以使用函数: int _unlink(const char *filename ); 
    获取当前路径是: char *_getcwd(char *buffer,   int maxlen );

     

    单字符宽字符互相转换

    #include <stdlib.h>
    size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n);   //转换单字符串为宽字符串
    size_t wcstombs(char *mbstr,   const wchar_t *wcstr,   size_t count );  //转换宽字符串为单字符串
    例如:
    CString str = L"hello";
    char sss[20];
    wcstombs(sss,str.GetBuffer(),20);              //转换宽字符为单字符
     
    stl 宽字符到 单字符转换
    wstring str = L"Hello";
     std::wstring::size_type len = str.length();
     std::string s(len*2,0);
     size_t total = wcstombs(&s[0],str.c_str(),len*2);
     s[total] = '\0';
     return s;


    mbtowc 和 wctomb 是单个字符相互转换
     int len;
     setlocale (LC_ALL, "chs");  //设置为简体中文环境
     wchar_t wc = L'中';
     wprintf(L"1个宽中文字符:%c \n",wc);
     char* p = "中";
     len = mbtowc (&wc, p, MB_LEN_MAX);
     wprintf(L"单字符串转换为1个宽字符:%c 长度: %d\n",wc,len);
     char pcmb[MB_LEN_MAX];
     len = wctomb (pcmb, wc);
     pcmb[len] = 0;
     printf("宽字符转换为单字符串:%s 长度:%d\n",pcmb,len);
     
    BYTE utf8[1024];        //utf8 字符串
    wchar_t wstr[1024];
    char mstr[1024];
    //UTF-8 转换为宽字符
    MultiByteToWideChar( CP_UTF8, 0, utf8,1024, wstr, sizeof(wstr)/sizeof(wstr[0]) );
    WideCharToMultiByte( CP_ACP,0,wstr,-1,mstr,1024,NULL,NULL );
       
    October 15

    netting c++

    引用 lippman 文章
    In my last column, we successfully wrapped my native Text Query Language (TQL) application for processing natural language texts. That is, the application executed correctly, but I did make a number of subtle program design errors that I’d like to correct in this column. For those who haven’t incised the code in memory, Figure 1 shows how we left it.

    #include "TextQuery.h"
    
    ref class TQL {
    public:
        // constructor creates a TextQuery object on native heap
        // destructor frees it ...
        TQL()  { pTQuery = new TextQuery; }
        ~TQL() { delete pTQuery;          }
    
        // the operations we wish to publish within .NET
        // these will generally be inlined by the compiler
    
        void build_up_text(){ pTQuery->build_up_text(); }
        void query_text()   { pTQuery->query_text();    }
    
    private:
          // our native object through which to invoke
          // our application compiled in Section 1 ...
        TextQuery *pTQuery;
    };
    
    // Our Managed TQL Wrapper Put to Work ...
    int main()
    {
        TQL ^tq = gcnew TQL;
    
        tq->build_up_text(); 
        tq->query_text();
    
          return 0;
    }
    

    The first problem is pretty trivial, but it would be embarrassing should the class be made generally available because all types within a module default to internal scope. That is, they can’t be seen from outside the module. This is a good default because it prevents the global namespace of the application from being polluted by module-only types. You must explicitly declare as public all classes you want to make visible across modules:

    public ref class TQL {
    public:
        // no change ...
    };
    

    This also holds for native classes you compile under C++/CLI, which is the opposite of C++ visibility of native types. So if you need your native class to be public across modules, you’ll also need to label that class public. This is an extension to ISO-C++ in Visual C++® 2005.

    The second problem involves cleaning up resources other than memory from the common language runtime (CLR) heap, which is garbage collected. A garbage-collected heap has deep ramifications for destructors, and that’s where we’ll start. Under the Microsoft® .NET Framework, there is no notion of a destructor, so we’ve artificially mapped destruction to .NET, as I’ll explain shortly.

    First, remember that destruction is a two-phase operation. When you delete a pointer, for example, internally the compiler first checks to see that the pointer is not null. If it is, nothing else happens. Otherwise, the compiler invokes the destructor associated with the object being addressed. If that concludes without an exception, the actual heap memory is freed. For CLR types, this two-phase operation is carried out by two different components. The invocation of the destructor is carried out by the compiler. The actual freeing of the memory is always handled by the garbage collector. When you invoke a destructor in .NET, the actual memory associated with the reference object is not yet reclaimed and won’t be until the garbage collector kicks in.

    So, the problem with my initial implementation is that while each TQL object will eventually be reclaimed by the garbage collector, the second phase of destruction, it will not have the associated destructor invoked, the first and (from my point of view) more important phase of destruction. Before the memory associated with an object is reclaimed by the garbage collector, an associated Finalize method, if present, is invoked. Think of this method as a kind of super-destructor since it is not tied to the program lifetime of the object. This is referred to as finalization. The timing of when or whether a Finalize method is invoked remains undefined, meaning that garbage collection exhibits nondeterministic finalization.

    Nondeterministic finalization works well with dynamic memory management. When available memory becomes sufficiently scarce, the garbage collector kicks in and things pretty much just work. Under a garbage-collected environment, destructors to free memory are unnecessary.

    Nondeterministic finalization does not work well, however, when an object maintains a critical resource such as a database connection, native heap memory, or a lock of some sort. In such cases, you need to release that resource as soon as possible. In the native world, that is done through the pairing of a constructor and a destructor. As soon as the lifetime of the object ends, either through the completion of the local block within which it is declared or through the unraveling of the stack because of a thrown exception, the destructor kicks in and the resource is automatically released. This approach works very well, but was unfortunately missing in the CLR world.

    It soon became clear that programmers using .NET needed a canonical way of indicating that the potentially scarce resources held by objects of a type need to be quickly disposed of, and the design solution is the System::IDisposable interface with a single Dispose method that contains the clean-up code. The primary drawback of this solution is that Dispose requires an explicit invocation by the user. This is potentially error-prone and therefore a step backwards. The C# language provides a modest form of automation through a special using statement which, when followed correctly, generates the call to Dispose for the objects within the statement.

    Beginning with Visual Studio® 2005, Visual C++ instead translates the class destructor into the class Dispose method. The destructor is renamed internally to the Dispose method and the reference class is automatically extended to implement the IDispose interface:

    // internal transformation of destructor under C++/CLI
    public ref class TQL : IDisposable 
    {
    ...
    void Dispose() { 
        // suppress finalize method for this object 
        // then generate the user code ...
        System::GC::SuppressFinalize(this);
        delete pTQuery;
    }
    };
    

    When either a destructor is invoked explicitly under C++/CLI or when delete is applied to a tracking handle, the underlying Dispose method is invoked automatically. If it is a derived class, a call of the Dispose method of the base class is inserted at the close of the synthesized method.

    While this transformation is a good thing, by itself it’s not good enough. First, reference objects don’t have scope constraints, so without explicitly having the programmer delete the reference object, the destructor doesn’t get invoked. Second, since the destructor now goes to Dispose rather than Finalize, the garbage collector is without any method to invoke. So, at first glance, this design change seems to have been a mistake!

    It is certainly the mistake I made in the implementation. As a native programmer, I didn’t realize that a destructor under .NET isn’t a complete solution to managing objects, due to the nature of garbage collection. Therefore, I didn’t think to explicitly provide a finalization function for the garbage collector to invoke. For just one object, probably nobody would notice. In a running system spawning a new TQL object for every query session, it would be quite an embarrassment. For your convenience, I redisplay the declaration of the TQL object:

    // the tq object never invokes the destructor ...
    // and we have not provided a finalize method ...
    // so the native memory held by tq is never freed ...
    int main()
    {
        TQL ^tq = gcnew TQL;
    
        tq->build_up_text();
        tq->query_text();
    
        return 0;
    }
    

    You need to provide a finalizer, and I’ll show you how in a minute. First let’s walk through how the CLR features in Visual C++ simulate deterministic finalization—by syntactically binding a reference object to a local or class scope; each represents a deterministic lifetime. The tricky part is that .NET itself doesn’t support this, and so we had to be clever.

    Visual C++ supports the declaration of an object of a reference class on the local stack or as a member of a class by declaring the object using the type name but without requiring its formal top hat (^). All uses of the object, such as invoking a member function, are done through the member selection dot (.) rather than arrow (->). At the end of the block, the associated destructor, transformed into Dispose, is invoked automatically:

    // OK, this invokes our destructor ...
    int main()
    {
        TQL tq;
    
        tq.build_up_text();
        tq.query_text();
    
        // destructor is invoked here ...
    
        return 0;
    }
    
    For those with more of a library bent to your syntax there is an auto-handle<> template that functions equivalently. (I prefer going hatless in my designs.) As with the using statement within C#, this is syntactic sugar rather than defiance of the underlying .NET constraint that all reference types must be allocated on the CLR heap. The underlying semantics remain unchanged, except that the invocation of the destructor is automatic. In effect, the destructor is again paired with constructors as an automated acquisition/release mechanism tied to an object’s lifetime.

    The problem with this solution is that you can’t force programmers to use it and, therefore, you can’t safely provide a destructor without providing a finalizer as well. Here’s how to do that:

    public ref class TQL {
    public:
        // constructor creates a TextQuery object on native heap
        TQL()  { pTQuery = new TextQuery; }
    
        // destructor frees it 
        ~TQL() { delete pTQuery;          }
        
        // finalizer frees it, called by garbage collector if 
        // destructor is not invoked ...
        !TQL() { delete pTQuery;          }
    };
    
    The ! prefix is meant to suggest the analogous tilde (~) that introduces a class destructor—that is, both post-lifetime methods have a token prefixing the name of the class. If the synthesized Finalize method occurs within a derived class, an invocation of the base class Finalize method is inserted at its end. If the destructor is explicitly invoked, the finalizer is suppressed.

    There are some remaining issues, as always, with these sorts of language designs. A finalizer, in general, is inefficient. (For a great discussion of this issue, see the book CLR via C#, Second Edition, by Jeffrey Richter.) When possible, it’s preferable not to define one. But currently there is no way to restrict a class containing a destructor from always having its objects defined such that deterministic finalization is guaranteed. I can’t force users of my class to always declare local reference objects:

    void f()
    {
        TQL t;     // ok, guaranteed to be disposed
        TQL ^ht;   // oops. No guarantee. Need a finalizer, then ...
        ...
    }
    

    One possibility would be to play with the accessibility of the finalizer to indicate whether you want to allow users to invoke it. That is, placing a finalizer in a private section would indicate that you are disallowing the use of objects outside of bound scope. However, in the current extensions design, all finalizers are public, regardless of the accessibility the user specifies.

    Therefore, the finalizer always seems to be necessary as a failsafe companion definition to a reference class just as, canonically, you generally need to pair a copy constructor with a copy assignment operator or an operator new with an operator delete.

    The other remaining issue, admittedly trivial, is that the code for the destructor and finalizer tends to be the same and, therefore, it is vexing to some of us to have to either duplicate the code or fret over how it should canonically be designed. Michael Vanier, a programming language professor at CalTech, suggested a ~! syntax to indicate to the compiler that it should use this code to support both destructor and finalization. I really like that idea, and perhaps the ECMA C++/CLI committee will too in some future revision!

    So, for a small piece of code, there were quite a few mistakes. Why? I think it’s because these issues—visibility of types, nondeterministic finalization—don’t exist in native programming and, therefore, represent leaks in our thinking about .NET. Next time we’ll look at regular expressions—in the quest to turn verbose C++/CLI into pithy Perl. Until then, may your code compile without error and execute to completion swiftly and correctly.

    October 05

    库设计常用方法

    1. placement new 方法
    如:pi = new (p) int; //placement new
        括号里的参数是一个指针,它指向一个内存缓冲器,new操作将在这个缓冲器上分配一个对象。Placement new的返回值是这个被构造对象的地址(比如扣号中的传递参数)。placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;
        STL 标准库中 allocator 空间分配器, 就使用了这个语法。
    先使用 malloc 分配内存p. 然后 在该内存内构造自己的类对象   new(p) T1(value) ; T1 是构造的类型,value 是类型的值
    如果是类退出需要显示调用析构函数。pi->~pi();free(p);
    2. naked function
    当你的函数内部较简单,用不到太多寄存器。或者需要根据上下文决定一些寄存器变量的时候。可以用naked 函数。对于一般的函数,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。使用naked函数,我们可以自己编写干干净净的函数,特别是当你代码没有使用这些寄存器的时候。可以像内联函数一样,省略一些操作。              
    void __declspec(naked) MyNakedFunction()
    {
        // Naked functions must provide their own prolog.
        __asm
        {
               PUSH EBP
               MOV ESP, EBP
               SUB ESP, __LOCAL_SIZE
         }
        // And we must provide epilog.
        __asm
        {
              POP EBP
              RET
         }
    }
    3. 指向类成员函数的指针
    在MFC库中很多窗口基类中调用派生类的消息处理函数。因为windows消息众多,如果为每个消息都设立虚函数,而每个虚函数都占用4 bytes,那样很多类体积将非常庞大。而且有些地方基类设计时考虑到派生类在此可能要作些处理,又不想使用虚函数的话也可以使用指向成员函数的指针。如下面的类。如果派生类没有修改pf成员,在release中  (this->*pf)() 将被优化掉。而且使用指向成员函数的指针也不用定死函数名字.
    class animate
    {
    typedef void (animate::*pfun)();
    public:
        animate() {pf=static_cast<pfun>(donothing);}
        void sleep()
        {   
            cout<<"animate sleep"<<endl;
            (this->*pf)();       
        }
        void donothing(){}
    pfun pf;
    };

    class fish:public animate
    {
    public:
        fish() {pf = static_cast<pfun> (test);}
        void test()
        {
            cout<<"使用基类指针调用派生类非虚函数"<<endl;
        }
    };
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        animate* fs = new fish;   
        fs->sleep();   
        delete fs;
        return 0;
    }
    4 __declspec(novtable) 
        C++中没有提供类似interface这样的关键字来定义接口,但是Mircrosoft c++中提供了__declspec(novtable)来修饰一个类,来表示该类没有虚函数表,也就是虚函数都(应该)是纯虚的
           声明的基类没有 vtable(虚拟函数表), 但还是有vptr指针(指向虚拟函数表)的。所以a1* a= new a1; a->hello()执行将会出错 。 而从a1派生的b1是有vtable的。所以 a1* a = new b1; a->hello() 将调用 b1 的hello函数。如果b1未定义hello函数,那么将调用 a1 函数。其实还是从b1对象的vptr指向的vtable中索引的函数地址。编译时由于b1中没有定义hello虚函数.   vtable 中虚函数hello指向的是a1类hello函数地址。良好的面向对象设计应该由派生类完成实现。面向对象规则第一条就是基于接口编程。所以将a1 函数均定义为纯虚函数,a1相当于接口,由派生类b1来完成实现,这样将抽象与实现之间解耦合,更符合面向对象规范,而接口a1也可以不生成vtable
           这样定义的类a1不能调用虚函数。所以a1 类应看成接口。不应生成 a1 类对象。同样程序生成时将节省a1 类 vtable 的空间。如果有大量虚函数,节省空间还是可观的 函数个数*4.
         次关键字为vc 所有,其他c++编译器并不支持
                                                                                                            
    class __declspec(novtable) a1
    {
    public:
        a1() {}
        virtual void hello()
        {
            cout<<"a hello"<<endl;
        }
    };
    class b1:public a1
    {
    public :
        b1() {}
        void hello()
        {
            cout<<"b hello"<<endl;
        }
    };