Inside the C++ Object Model 读书笔记(六)

Runtime Semantics

All globally visible objects in C++ are placed within the program data segment.
In C, a global object can be initialized only by a constant expression, that is, one that can be evaluated at compile time.

6.1 Object Construction and Destruction

Global Objects

When cfront was the only C++ implementation and portability across machines was more important than efficiency, the following munch strategy emerged:

  1. Within each file that requires static initialization, generate an __sti() function containing the necessary constructor invocations or inline expansions. identity, for example, would cause the following __sti() function to be generated within the file matrix.c:
__sti__matrix_c__identity() {
      
   // Pseudo C++ Code 
   identity.Matrix::Matrix(); 
} 

where __matrix_c is an encoding of the file name and __identity represents the first nonstatic object defined within the file. Appending these two names to __sti provided a unique identifier within the executable. (Andy Koenig and Bjarne worked out this “fake static” encoding scheme in response to name-clash agonies reported by Jim Coplien.)

  1. Similarly, within each file that requires a static deallocation, generate an __std() function containing the necessary destructor invocations or inline expansions. In our example, an __std() function is generated to invoke the Matrix destructor on identity.

  2. Provide a set of runtime library munch functions: a _main() function to invoke all the __sti() functions within the executable and an exit() function to analogously invoke all the __std() functions.

cfront inserted a _main()call as the new first statement within main(). The exit() function rather than the C library exit() function was linked in by cfront’s CC command by placing the C++ standard library first on the command line.

Our solution was to use the nm command. (nm dumps the object file symbol table entries.) An executable was generated from the .o files. nm was then run on the resulting executable. Its output was piped into the munch program. (I think Rob Murray wrote munch, but nobody any longer claims to remember.) munch munched the symbol table names, looking for names that began with __sti or __std. (Yes, periodically to amuse ourselves we would begin a function with __sti such as __sti_ha_fooled_you). It then added the function names to a jump table of __sti() and __std() functions. Next, it wrote the tables into a small program text file. Then, odd as it may sound, the CC command was reinvoked to compile the file containing the generated tables. The entire executable was then relinked. _main() and exit() traversed the respective tables invoking each entry in turn.

The addition of support for static initialization of nonclass objects in part was a side effect of supporting virtual base classes.

the location of the virtual base class Point subobject fluctuates with each subsequently derived class and therefore cannot be set during compilation.

Local Static Objects

In cfront. First, a temporary was introduced to guard mat_identity’s initialization. On the first pass through identity(), the temporary evaluated as false. Then the constructor was invoked, and the temporary was set to true.

// generated temporary static object guard 
static struct Matrix *__0__F3 = 0 ; 

// the C analog to a reference is a pointer 
// identity()'s name is mangled based on signature 

struct Matrix* 
identity__Fv () 
{
      
   // the __1 reflects the lexical level 
   // this permitted support for code such as 
   // int val; 
   // int f() { int val; 
   //           return val + ::val; } 
   // where the last line becomes 
   //    ....return __1val + val; 

   static struct Matrix __1mat_identity ; 
   // if the guard is set, do nothing, else 
   // (a) invoke the constructor: __ct__6MatrixFv 
   // (b) set the guard to address the object 
   __0__F3 
      ? 0 
      :(__ct__1MatrixFv ( & __1mat_identity ), 
       (__0__F3 = (&__1mat_identity))); 
   ... 
} 


char __std__stat_0_c_j () 
{
      
   __0__F3 
      ? __dt__6MatrixFv( __0__F3 , 2) 
      : 0 ; 
   ... 
} 

Arrays of Objects

In cfront, we used one instance of a function we named vec_new() to support creation and initialization of arrays of class objects.

void* vec_new( 
   void *array,      // address of start of array 
   size_t elem_size, // size of each class object 
   int elem_count,   // number of elements in array 
   void (*constructor)( void* ), 
   void (*destructor)( void*, char ) 
} 

an analogous vec_delete() (or vec_vdelete() for classes with virtual base classes) runtime library functions. (Sun separates out handling the deallocation of named versus dynamically allocated arrays.) Its signature is generally the following:

void* vec_delete( 
   void *array,      // address of start of array 
   size_t elem_size, // size of each class object 
   int elem_count,   // number of elements in array 
   void (*destructor)( void*, char ) 
} 

Default Constructors and Arrays

At the programmer level, taking the address of a constructor is not permitted. We elected within cfront to generate an internal stub constructor that takes no arguments. Within the body, the user-supplied constructor is invoked, with the default arguments made explicit.

// internally generated stub constructor 
// to support array construction 
complex::complex() 
{
      
   complex( 0.0, 0.0 ); 
} 

6.2 Operators new and delete

extern void* operator new( size_t size ) 
{
      
   if ( size == 0 ) 
        size = 1; 

   void *last_alloc; 
   while ( !( last_alloc = malloc( size ))) 
   {
      
      if ( _new_handler ) 
         ( *_new_handler )(); 
      else return 0; 
   } 

   return last_alloc; 
} 

Although it is legal to write new T[ 0 ];

the language requires that each invocation of operator new return a unique pointer. The conventional way of solving this is to return a pointer to a default 1-byte memory chunk (this is why size is set to 1). A second interesting element of the implementation is the need to allow the user-supplied _new_handler(), if present, to possibly free up memory. This is the reason for the loop each time _new_handler() is actually invoked

The Semantics of new Arrays

If a class defines a default constructor, however, some version of vec_new() is invoked to allocate and construct the array of class objects.

Point3d *p_array = new Point3d[ 10 ]; 
//is generally transformed into

Point3d *p_array; 

p_array = vec_new( 0, sizeof( Point3d ), 10, 
                  &Point3d::Point3d, 
                  &Point3d::~Point3d );

The Semantics of Placement Operator new

Automatic application of the constructor applied.
placement operator delete : applies the destructor to the object but does not free the memory. A direct call of the destructor is no longer necessary.

// the correct way of applying destructor 
p2w->~Point2w; 
p2w = new ( arena ) Point2w; 

In general, however, the placement new operator does not support polymorphism.

6.3 Temporary Objects

你可能感兴趣的:(C++)