Allocating and Deallocating Memory within ACIS

From DocR21

Jump to: navigation, search

Allocating heap memory and deallocating it properly is critical to any application. Within ACIS, this is especially true for a number of reasons:

  • There are a number of platforms which manage heap memory differently. When allocations/deallocations are done incorrectly, some platforms crash, some platforms leak, and others proceed silently.
  • ACIS has the ability to manage small blocks of heap memory, which we call free listing. Free listing is a heap management algorithm that provides a substantial performance increase, and is transparent to the end user. If the ACIS allocation/deallocation rules are not followed, a block might taken from the free list and freed by the OS, or vice versa, which will almost always result in a crash.
  • ACIS also has the ability to track all heap allocations and deletions. If the ACIS allocation/deallocation rules are not followed, some allocations show up as memory leaks—when they simply are not being freed properly.

Contents

Understanding False Memory Leak Reported in ACIS-based MFC Applications

False memory leaks are reports of leaks by memory management systems that can be proven to be erroneous. In this case Spatial specifically refers to the leak reports generated by the Microsoft debug CRT when an ACIS-based MFC application exits.

Faulty leak reports are generated when the runtime library that was loaded last exits, not when the runtime library that was loaded first exits. In other words, the faulty leaks are reported when the first runtime library exits, not when the last one exits. (This is a bug that has been acknowledged by Microsoft.) Moreover, other libraries may be unloaded after the first runtime library exits. These other libraries may free memory when they unload, which is the case with ACIS libraries. These deallocations can occur after the leak report has already been generated, in which case they are incorrectly reported as leaks.

No faulty leaks are reported when all runtime libraries are loaded before other libraries during application startup. This desirable behavior can be achieved by managing link order and by managing the types of run-times used by the application.

Libraries specified as additional dependencies in the project settings are loaded in the order specified before all other libraries. (This is referred to as "explicit linkage".) Adding the appropriate MFC runtime library to the beginning of the link dependencies should remove erroneous leaks in most cases. Additionally, you can load all libraries with the use of pragmas. This is the suggested way to link ACIS libraries into your application. (This is referred to as "implicit linkage".) From our experience, applications that link libraries implicitly are less likely to encounter faulty leak reports. Note that pragmas added to source code will, therefore, require project rebuilds.

Faulty leak reports can also occur when mixed versions of runtime libraries are loaded because of dependencies from other libraries. For example, a typical MFC application may use MFC80D.DLL and have dependencies on other libraries that use MFC80UD.DLL. The latter library may be loaded after other libraries have been loaded, specifically after ACIS libraries are loaded. This is a scenario for faulty leak reports since libraries are unloaded in reverse load order. This cannot be solved with explicit linkage because of link errors (such as multiply defined symbols) and mixed runtime issues. The best solution is to manage the types of run-times used by the application by using the same ones whenever possible.


ACIS Rules for Heap Allocations and Deletions

Rule 1: Never Allocate Memory in Static-initialization

This fundamental rule must not be violated. ACIS provides the ability for the customer to modify the run-time behavior of the ACIS memory management system. That is, the customer can decide, at run-time, to enable free listing or enable leak-tracking. Once initialize_base is called with a particular memory management configuration, that memory management configuration is set for the duration of the life of the application - even through the end of static-termination.

For example, suppose an ACIS_NEW takes place in static-initialization and the memory is allocated from the ACIS free list. Then suppose the customer application calls initialize_base and disables free listing. When the matching ACIS_DELETE is called, the pointer will be freed by the system, not by the ACIS free listing mechanism. The system will most likely crash because it is trying to delete a pointer of which it has no knowledge.

Allocating memory in static initialization will most likely result in a crash.

Rule 2: Use STD_CAST Appropriately

If ACIS_NEW is used to allocate a simple data-type, enum, or struct, or an array of simple data-types, enums, or structs then STD_CAST must be used while calling ACIS_DELETE. The STD_CAST macro must not be used while using ACIS_DELETE for deleting a C++ object or array of objects.

Example

SPAposition* p = ACIS_NEW SPAposition;
ACIS_DELETE p;
SPAposition* p = ACIS_NEW SPAposition[24];
ACIS_DELETE [] p;
SPAposition** p = ACIS_NEW SPAposition*;
ACIS_DELETE STD_CAST p;
SPAposition** p = ACIS_NEW SPAposition*[24];
ACIS_DELETE [] STD_CAST p;

Failing to use STD_CAST properly will most likely result in a false leak, but on some systems (for example, Mac) it will result in a crash.

Rule 3: Use the Array Operator Appropriately when Deleting

This is a rule of C++ programming. After allocating an array of elements using new/ACIS_NEW, the array operator must be used when calling delete/ ACIS_DELETE.

Example

int* n = new int[6]; 
delete [] n; 
my_object* mo = new my_object[10]; 
delete [] mo;

Failing to use the array operator when deleting may result in memory leaks and/or resource leaks and, in some cases, crashes.

Using STD_CAST

ANSI C++ permits overloading global new operator and even provides an overloaded global new operator that has parameters (placement new). ACIS leverages this feature to send file and line information with every call to ACIS_NEW, which allows ACIS to track memory leaks.

ACIS provides a version of ACIS_NEW that overloads global new to send along file and line information. If the class is derived from ACIS_OBJECT, ACIS provides a class-level overloaded new to distinguish between global new's and class-level new's behavior. This is an important distinction: these two allocations preprocess into completely different function calls.

// 
// This line would preprocess into the following: 
// 
// 
// BODY* b = BODY::operator new (size, __FILE__, __LINE__,''etc.'') BODY; 
// 
BODY* b = ACIS_NEW BODY; 
// 
// This line would preprocess into the following: 
// 
// int* n = operator new (size, __FILE__, __LINE__, ''etc.'') int; 
// 
int* n = ACIS_NEW int;

While ACIS_NEW converts into an overloaded new with file and line information, ACIS_DELETE does something different. If the class is derived from ACIS_OBJECT, ACIS_DELETE converts into a class-level overloaded delete. For simple data types, ACIS_DELETE converts into global delete. The last statement requires a critical understanding: deleting a simple data type using ACIS_DELETE is exactly the same as calling global delete.

A simple case:

//
// This line would preprocess into the following:
//
//
// BODY::operator delete (ptr, ''etc.'') b;
// 
ACIS_DELETE b;

A complex case that demonstrates the problem with not using STD_CAST:

//
// This line would preprocess into the following:
//
//
// delete n;
// this will show up as a false leak!!
//
ACIS_DELETE n;

Because ACIS does not (and should not) overload global delete, this will show up as a leak, even though the memory is being given back to the system. The correct version:

//
// This line would preprocess into the following:
//
//
// ACIS_STD_TYPE_OBJECT::operator delete n;
//
ACIS_DELETE STD_CAST n;

The ACIS_STD_TYPE_OBJECT is defined as follows:

class DECL_BASE ACIS_STD_TYPE_OBJECT {
public:
    void operator delete(void * alloc_ptr ) {
        acis_discard( alloc_ptr, eClassStd,0 );
    }
    void operator delete (void * alloc_ptr ) {
        acis_discard( alloc_ptr, eClassArrayStd,0 );
    }
};

Because the pointer is cast to an object that has an overloaded delete, the ACIS_DELETE call is redirected to the ACIS memory manager and handled properly.

See Also

Personal tools
Live