C++ Memory Management

In the case of C++, the ownership of memory is handled by the control class and message buffer objects. These classes share a context structure and use reference counting to manage the allocation and release of the context block. When a message buffer object is created, a context block structure is created as well. When this object is then passed into a control class constructor, its reference count is incremented. Then when either the control class object or message buffer object are deleted or go out of scope, the count is decremented. When the count goes to zero (i.e. when both the message buffer object and control class object go away) the context structure is released.

What this means to the user is that a control class or message buffer object must be kept in scope when using a data structure associated with that class. A common mistake is to try and pass a data variable out of a method and use it after the control and message buffer objects go out of scope. For example, consider the following code fragment:

   ASN1T_<type>* func2 () {
      ASN1T_<type>* p = new ASN1T_<type> ();
      ASN1BERDecodeBuffer decbuf;
      ASN1C_<type> cc (decbuf, *p);

      cc.Decode();

      // After return, cc and decbuf go out of scope; therefore
      // all memory allocated within struct p is released..

      return p;
   }

   void func1 () {
      ASN1T_<type>* pType = func2 ();

      // pType is not usable at this point because dynamic memory
      // has been released..
   }

As can be seen from this example, once func2 exits, all memory that was allocated by the decode function will be released. Therefore, any items that require dynamic memory within the data variable will be in an undefined state.

An exception to this rule occurs when the type of the message being decoded is a Protocol Data Unit (PDU). These are the main message types in a specification. The ASN1C compiler designates types that are not referenced by any other types as PDU types. This behavior can be overridden by using the -pdu command line argument or <isPDU> configuration file element.

The significance of PDU types is that generated classes for these types are derived from the ASN1TPDU base class. This class holds a reference to a context object. The context object is set by Decode and copy methods. Thus, even if control class and message buffer objects go out of scope, the memory will not be freed until the destructor of an ASN1TPDU inherited class is called. The example above will work correctly without any modifications in this case.

Another way to keep data is to make a copy of the decoded object before it goes out of scope. A method called newCopy is also generated in the control class for these types which can be used to create a copy of the decoded object. This copy of the object will persist after the control class and message buffer objects are deleted. The returned object can be deleted using the standard C++ delete operator when it is no longer needed.

Returning to the example above, it can be made to work if the type being decoded is a PDU type by doing the following:

   ASN1T_<type>* func2 () {
      ASN1T_<type> msgdata;
      ASN1BERDecodeBuffer decbuf;
      ASN1C_<type> cc (decbuf, msgdata);

      cc.Decode();

      // Use newCopy to return a copy of the decoded item..

      return cc.newCopy();
   }