ctordtor.txt

来自「开放源码的编译器open watcom 1.6.0版的源代码」· 文本 代码 · 共 579 行 · 第 1/2 页

TXT
579
字号
Ctor/Dtor Design
================

This file documents the use the constructors (CTOR) and destructors (DTOR).
It includes the design for error-handling.


History
-------

91/11/21 - JWW - initial design


Common Stuff
============

    typedef void CTOR_DTOR();       // ctor/dtor function

    struct THREAD_CTL               // controls each execution thread
    {   CTOR_BLOCK_LIST *autos;     // - active CTOR blocks for autos
        TRY_BLOCK_REF *tries;       // - active try blocks
        char exception[1];          // - exception (variable-sized)
    }

Run-time Globals
================

    THREAD_CTL thread;              // current execution thread


Automatic Objects
=================

- CTOR called for object as the definition is executed
- corresponding DTOR must be called when block is exited (or popped by error
  handling)

- need structure to indicate which items have been CTOR'd (view block
  as C++ object which needs to CTOR'd on entry, DTOR'd on exit, and is
  modified as each automatic object in the scope has been CTOR'd)

    struct CTOR_BLOCK_LIST          // list of items CTOR'd (on stack)
        CTOR_BLOCK_LIST *prev;      // - previous block
        BLOCK_DTORS *dtors;         // - destructors list
        CTOR_BLOCK_ITEM *list[1];   // - list of elements CTOR'd
    };

    struct BLOCK_DTORS              // items to be dtor'd (static r/o)
    {   unsigned count;             // - # objects
        CTOR_DTOR *list[1];         // - dtor's
    };

    - the two lists must correspond

- implementation:

    - CTOR_BLOCK_LIST allocated as an automatic object (on stack) for each
      block (if req'd)

    - CTOR'd on block entry by:
        ctor_block_list( CTOR_BLOCK_LIST *list
                       , BLOCK_DTORS *dtors
                       , unsigned count );
        - all item pointers are set to NULL to indicate not CTOR'd
        - links to previous block for error handling

    - DTOR'd on block exit by:
        dtor_block_list( CTOR_BLOCK_LIST *list );
        dtor_block_list_curr();

    - after a successful CTOR of an item:
         ctor_block_item( CTOR_BLOCK_LIST *list
                        , void *item
                        , unsigned index );
         ctor_block_item_curr( void *item
                             , unsigned index );

- method:

    - goto out of block
        - generates "IC_BLOCK_EXIT scope" for each scope exited

    - execution of definition:
        - generates "IC_BLOCK_CTOR sym" after each class variable CTOR'D

    - execution of IC_BLOCK_OPEN:
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - allocates automatic variable if req'd

    - execution of IC_BLOCK_EXIT
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - generates call to dtor_block_item_curr, if req'd

    - execution of IC_BLOCK_CLOSE
        - inspects scope to see if CTOR_BLOCK_LIST req'd
        - generates call to dtor_block_item_curr, if req'd

    - execution of IC_BLOCK_CTOR
        - generates call to ctor_block_item_curr

    - note: perhaps we should set flag somewhere to indicate that a class
            element CTOR has occurred in a scope (this could be done when
            IC_BLOCK_CTOR is generated by the front-end)


Error Handling
==============

- type of throw expression determines which of the active catch-blocks
  receives control

- there is a need to uniquely identify (CATCH_NAME) each possible type
  that can be caught

    typedef char *CATCH_NAME;

- implementation:

    - in a COMDEF generate a unique 1-byte global variable whose name
      corresponds to the type (note: T, const T, T&, const T& cause
      matches )
        - two names are generated for bare-name references
        - if we never use the storage in the COMDEF, the linker could be
          upgraded to support "link-time enumeration"
        - we could use the storage to speed the search for a catch


- need structure to record an active try-block:

    struct TRY_BLOCK_REF            // reference (on stack) to try block
    {   TRY_BLOCK_REF *prev;        // - previous active block
        TRY_BLOCK *try_block;       // - reference to block
    }

    struct TRY_BLOCK                // an active try (static r/o)
    {   unsigned count;             // - # catch blocks
        CATCH_ITEM list[1];         // - list of catch blocks
    }

    struct CATCH_ITEM               // an active catch block for a try
        void (*rtn)();              // - start of code
        CATCH_NAME catch_name;      // - unique name
    }


- need structure to record possible throw expressions which can be caught:

    struct THROW_LIST               // list of expression types (static r/o)
    {   unsigned count;             // - # possible expressions
        THROW_EXPR list[1];         // - possible expressions
    }

    struct THROW_EXPR               // define possible expression (static r/o)
    {   void (*copy)();             // - copy routine
        CATCH_NAME catch_name;      // - unique name
    };

- implementation:

    - view try block as C++ object

    - block entry:
        ctor_try_block( TRY_BLOCK_REF *ref, TRY_BLOCK *try_block );

    - block exit:
        dtor_try_block( TRY_BLOCK_REF *ref );
        dtor_try_block_curr();

- throwing an exception

    - how does a DTOR get called for the expression ?

    - if no expression:
        - if error-handling active, call rethrow_exception();
            - does this use the converted expression ?
            - if no, a new THROW_LIST is required
        - else call unexpected()
    - else causes call:
        - if error-handling active
            - do we free old exception
        - call throw_exception( THROW_LIST *list, expression );

    - search back through TRY_BLOCK_REF blocks to locate CATCH_BLOCK and the
      TRY_BLOCK_REF for the item.

    - if none: call unexpected();

    - unwind stack (described later) and jump to catch block

- catch block processing: same as any other block


Compilation
===========

try {                       ctor_try_block( &try_block );
                            ... stuff for { ... }
}                           goto lab_4;

                        lab_1:
catch( object &o ) {        object internal;
                            object &o = internal;
                            ctor_catch( &internal );
                            [ ctor_block_item_curr( &o, index ); ] if req'd
                            ... stuff for { ... }
                            [ dtor_block_item_curr(); ] if req'd
}                           goto lab_4;

                        lab_2:
catch( object o ) {         object o;
                            ctor_catch( &o );
                            [ ctor_block_item_curr( &o, index ); ] if req'd
                            ... stuff for { ... }
                            [ dtor_block_item_curr(); ] if req'd
}                           goto lab_4;

                        lab_3:
catch( ... ) {              ... stuff for { ... }
                            goto lab_4;
}
                        lab_4;

- the copy constructor for the indicated object is invoked by the run-time
  routine "ctor_catch"


Stack Unwinding:
================

- the active CTOR_BLOCK_LIST items must be processed until one preceding
  the active try block is encountered

- for each such CTOR_BLOCK_LIST, call the appropriate destructor for the
  CTOR'd items


Compiling CTOR's
=================

- housekeeping may be required:
    - have to set up pointers to virtual base tables
    - have to set up pointers to virtual function tables

- a constructor may only partially initialize an object before an exception
  is thrown

- the initialized parts must be destructed (in reverse order)

- we need structures to record what has been initialized:
    - use CTOR_BLOCK_LIST, BLOCK_DTORS to record this

- this can be accomplished by using a second CTOR_BLOCK_LIST within the
  outermost block of a constructor; each constructor call is followed by an
  appropriate "ctor_block_item_curr" call

- no modifications are required for stack unwinding

- note: the same BLOCK_DTORS is used for all ctor's/dtor's for a class (there
        need be only one per class)

- implementation: ctor (can be many)

    - to start, generate everthing in-line (what follows is optimized version)

    - in declaration order, generate initialization for base classes, then
      for class members, then generate function-body code

        - if mem-initializers exist, (base classes, class members)
            - fill in VFPTR pointers if required
            - generate in-line initialization code based on specifications
          else
            - if more than one ctor exists
                - fill in VBPTR, VFPTR pointers if required
                - call generated-default-ctor
              else
                - generate default ctor in-line

        - all virtual base classes (pg.92) are constructed before any
          non-virtual base classes
            - second parameter (0,1) passed to such constructors; when 1, the
              exact type of the class is known
                - virtual bases are constructed
                - construct all virtuals
            - subsequent calls to constructors pass 0 to ensure that the
              virtuals are not re-initialized

    - generated-default-ctor: internally-named routine which is equivalent to
      C::C() when this constructor does not exist (so, it must have a
      different name than C::C() that is specified.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?