13. MEMORY MANAGEMENT AND SAVING ON DISK

13.1 Virtual Functions and Internal Pointers
13.2 Memory Management (ALLOCATE/FREE)
13.3 Saving to Disk (SAVE/OPEN)
13.4 Collecting Objects (SWEEP)

13.1 VIRTUAL FUNCTIONS AND INTERNAL POINTERS

Familiarity with this section is not essential for use of the library. It only provides more depth and theoretical background. If you are just starting, and you want to use DOL quickly, skip this chapter and proceed directly to Chap. 13.2.

When you are saving objects to disk, the program must correctly save and retrieve three parts of the object:

  1. General attributes, int and float types.
  2. Pointers that form your data structures.
  3. Virtual function pointers and virtual base class pointers which C++ compilers adds trasparently to C++ objects. These pointers may be different and in different parts of the object depending on the platform and the compiler, and most programmers are not aware about their existence.

All this has to be done recursively for all member objects of the given class, and following the inheritance hierarchy. The fact that C++ allows multiple inheritance complicates this even more.

We will not explain in full detail how this is done, only the overall concept. If you understand the underlying organization, you will appreciate what DOL can do for you. To implement persistent data is a complex task in any language, especially in C++.

Let us look at this example:

    class Obj2 { // base object
    ZZ_EXT_Obj2
public: 
    virtual prt(void){ };
    virtual save(Obj2 *p){ };
};
class Obj1 : public Obj2 { 
    int i;
    ZZ_EXT_Obj1
public:
    Obj4 m; // member object, not pointer
    virtual prt(void){cout <<<< i;};
    virtual save(Obj2 *p){ .... };
};
    class Obj3 { // another base object
    ZZ_EXT_Obj3
public:
    virtual abc(int i){ };
};
    class Obj4 : public Obj3 {
    ZZ_EXT_Obj4
    int k;
    char *p;
public:
    virtual abc(int i){ ... );
};
    class Obj5 { // simple, not interesting
    ZZ_EXT_Obj5
    ...
};
ZZ_HYPER_DOUBLE_RING(myRing,Obj2);
ZZ_HYPER_SINGLE_TRIANGLE(myTria,Obj4,Obj5);

A typical internal representation of one Obj1 may be like this (depending on the C++ compiler):

fwd ring pointer (ZZ_EXT_Obj2)
bwd ring pointer (ZZ_EXT_Obj2)
vf virt.function pointer Obj2
i integer in Obj1
vf virt.function pointer Obj3
child pointer (ZZ_EXT_Obj4)
k integer in Obj4
p character pointer in Obj4

When virtual base class (abstract class) is used, one more internal pointer is present, subject to different rules than the pointers shown here.

When restored from disk, objects are in new memory locations, therefore pointers fwd, bwd, and child must be modified (swizzled). If you store an object during one program run, and restore it in another run, the virtual function tables will also be in a different location. The remaining attributes should keep their values (i,k), with perhaps the exception of some attributes, such as p here, which is only a temporary variable, and does not require permanent storage.

Note several interesting details: There is only one virtual function pointer in Obj2, even though two virtual functions are present. Some objects (Obj1,Obj4) do not have any virtual function pointers in spite of using virtual functions.

The value of the virtual pointer is the same for all objects of the same type, however, it is different, for example, for a standalone Obj2, and for Obj2 which has been inherited by Obj1. At the time of the writing, some C++ compilers (e.g. the AT&T compiler) place the virtual function pointer at the end of the object. However, the Microsoft VC++ compiler places it at its beginning.

Typically, inheritance inserts the base class at the beginning of the object. Pointers which form the data organization, and which are hidden under ZZ_EXT statements, are dispersed throughout the object in the order in which they occur in your class definition.

In order to manage the whole situation, DOL must know:

  1. the inheritance/member hierarchy (ZZ_EXT statements and class declarations provide this information);
  2. which classes have virtual functions;
  3. where the pointers that need an update are (ZZ_EXT statements hide them).

When you create a new object, data pointers must be initialized to NULL (disconnected pointers), while virtual function pointers are set to the same values for each object of the given class. When you allocate all objects from the heap (using the new() operator, DOL does this automatically. However, if your data organization automatically allocated objects, you must include the ZZ_INIT() statement in all your constructors. For example:

    class Blob {
        int speed;
        ...
    public:
        Blob(){ZZ_INIT(Blob);}
        Blob(int s){ZZ_INIT(Blob); speed=s;}
        ...
    };
    
    int main(){
        Blob b1,b2(80); // automatically allocated objects
        Blob *b3,*b4; // not objects, just references
        ...
        b3=new Blob; // allocated from the heap
        b4=new Blob(120); // allocated from the heap

It is much safer to avoid automatically allocated objects and simply forget about ZZ_INIT().

When saving data to disk in the ASCII or binary modes, two pieces of information are saved for every object: (1) A fix-sized header which stores the old location, object type (class) and the object size (the object can be an array). (2) The object itself.

In the binary mode, the object is saved as a block of bytes, which is very fast. In the ASCII mode, a simple object is saved as two records. The first record includes all data structure pointers (DOL knows where they are and what are their values); the second record includes all the members you specified in the ZZ_FORMAT() statement for the particular class. When storing a composite objects which involves inheritance and/or member objects, there is a similar record for each part. When retrieving the information from disk, the program builds an internal table of old/new addresses for all objects, and converts all the pointers.

The third mode of storing data to disk, called memory blasting is conceptually different, and the next chapters explain both the algorithm and its use.

 

Chapter 12: Documentation Next Section 13.2 Memory Management