TOP

General Concepts

Memory Systems

The ASN.1/C++ runtime makes heavy use of dynamic memory, both for representation class instances and for temporary storage. For most platforms and applications, the standard C dynamic memory management (malloc/free/realloc) is good enough. However, on some platforms the standard memory management is relatively heavyweight (e.g., because of the need of inter-thread synchronization) and may prevent achieving the needed performance. There may also be some other situations where the application programmer would prefer non-standard, customized memory management.

As stated in Memory Allocation, the runtime operates representation class instances using the asn1Malloc(), asn1Free(), and asn1Realloc() functions. The encoder/decoder also uses these functions to allocate and deallocate temporary storage. These functions invoke the methods of the current memory system object. A memory system is a special C++ object that specifies the memory management strategy that the ASN.1/C++ Runtime uses when dealing with representation objects and temporary storage. At any given moment, there is only one memory system (the current memory system) that processes all memory management requests from the ASN.1/C++ runtime. The default memory system, available when the application starts, simply uses the standard C memory management (malloc/free/realloc).

To change the memory management, you should create another memory system object. The constructor of the memory system object automatically makes it current; its destructor restores the previous current memory system. Therefore, you may create a stack of memory systems; however, we expect there is rarely a need to do so, if ever.

The current memory system pointer is global to all application threads. It is assumed that the custom memory system is set at the start of the application, before creating multiple threads. Access to the current memory system is not synchronized. If you plan to change the memory system when multiple threads are running, you should implement your own synchronization, however, we do not recommend changing memory systems in multi-threading mode.

If you allocate an object using a particular memory system, you must deallocate it using the same memory system. In particular, it means that if you initialize some representation objects before changing the memory system (e.g., statically), you should not deallocate them, change them, or use them in ownership-transferring functions until the new memory system is destroyed. A simpler rule is this: if you change the memory system, do so at the very beginning of the program, before starting multiple threads, and do not use static representation objects.

Memory Pools

The ASN.1/C++ runtime provides two memory systems: the default memory system that you need not create explicitly and the memory pool-enabled memory system. If you are using the latter, you can take advantage of memory pools.

A memory pool is a collection of memory chunks, the size of each being equal or greater than some specified value, allocated for exclusive use by one application thread. A thread may use more than one memory pool, but only one pool is active at a time and that pool is used for the allocation of new objects by that thread. If all of the existing pool's memory is exhausted, an additional memory chunk is requested from the system using malloc. Memory allocated to a pool can be cleaned at once and then reused, but is never returned to the system except when the pool is destroyed.

What is the reason for such a mode of operation? On some platforms the standard malloc/free are relatively expensive operations, especially in a multithreaded application, when it should perform inter-thread synchronization to avoid race conditions between threads competing for memory resources. The application may achieve better performance by requesting a big chunk of memory for each thread and allocating smaller memory blocks by splitting the preallocated big chunk. Memory pools provide this optimization almost transparently for the application programmer.

If you are using several memory pools in one thread, you may safely mix memory allocated from different pools. For example, you may insert (using an ownership-transferring function) an object that was allocated in one pool into another object that was allocated in another pool. However, you cannot mix objects allocated from pools belonging to different threads. Doing so may result in memory corruption because different threads are competing for one memory pool.

Replacing asn1Malloc() | asn1Free() | asn1Realloc()

The ASN.1/C++ runtime also supports the earlier method of customizing memory management – providing special implementation of the asn1Malloc(), asn1Free(), and asn1Realloc() functions to replace the standard implementation. In versions prior to 4.0.1, this was the only way to implement custom memory management. To use this method, you simply write the three functions as you wish and specify the object file in the linker command line before the ASN.1/C++ Runtime library, so the linker uses your object file instead of the version from the runtime library.

However, this approach has two important disadvantages:

  1. You cannot use it if you are using the ASN.1/C++ runtime as a Windows DLL.
  2. You cannot use memory pools when replacing asn1Malloc(), asn1Free(), asn1Realloc().

The preferred way to customize memory management is to implement your own memory system. Then you will avoid those disadvantages.


OssMemorySystem

The OssMemorySystem class is an abstract class that is a common ancestor of all memory systems.

Definition

class OssMemorySystem {
protected:
    OssMemorySystem *previous;
    OssMemorySystem();
public:
    virtual ~OssMemorySystem();
    static OssMemorySystem *get_current();
    virtual void *base_malloc(size_t size);
    virtual void base_free(void *ptr);
    virtual void *base_realloc(void *ptr, size_t oldsize, size_t size);
    virtual void *asn1_malloc(size_t size) = 0;
    virtual void asn1_free(void *ptr) = 0;
    virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size) = 0;
};

Methods

OssMemorySystem *previous;
This is a protected member that points to the previous memory system. The OssMemorySystem's destructor uses this pointer to restore the current memory system pointer to the previous value. It can also be used to implement one memory system on top of another one, if there is such a need.
OssMemorySystem();
This is a protected constructor that saves the current memory system in previous and registers the object being created as current.
~OssMemorySystem();
The destructor restores the current memory system pointer from the previous field.
static OssMemorySystem *get_current();
This is the static function that returns the pointer to the current memory system.
virtual void *base_malloc(size_t size);
This is the base memory allocation function. It is called to request the basic memory blocks from the OS or from the user-implemented memory storage. For example, the pool-enabled memory system uses it to allocate big memory chunks that will subsequently be split into smaller blocks. In OssMemorySystem, this function calls malloc, but you can override it if you wish. This function should not throw C++ exceptions. If the requested amount of memory is not available, it should return NULL.
virtual void base_free(void *ptr);
This is the base memory deallocation function. It is called to return the basic memory blocks to the OS, or to the user-implemented memory storage. In OssMemorySystem, this function calls free, but you can override it as you wish. This function should not throw C++ exceptions.
virtual void *base_realloc(void *ptr, size_t oldsize, size_t size);
This is the base memory reallocation function. It is called to resize the basic memory blocks. In OssMemorySystem, this function calls realloc, but you can override it as you wish. This function should not throw C++ exceptions. If the requested amount of memory is not available, it should return NULL. The arguments of this function are: ptr, the address of the memory block to be resized; oldsize, its size; and size, the new size of the block. The function returns the address of the new block, or the address of the old block if the block remains in its place.
virtual void *asn1_malloc(size_t size) = 0;
This is a pure virtual function that is to be overridden in the derived classes. It is the function that asn1Malloc() calls to allocate memory blocks for representation objects and temporary storage.
virtual void asn1_free(void *ptr) = 0;
This is a pure virtual function that is to be overridden in the derived classes. It is the function that asn1Free calls to deallocate memory blocks allocated for representation objects and temporary storage.
virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size) = 0;
This is a pure virtual function that is to be overridden in the derived classes. It is the function that asn1Realloc calls to change the sizes of memory blocks allocated for representation objects and temporary storage.

OssDefaultMemorySystem

The OssDefaultMemorySystem class is the default memory system. It is available without any action by the application programmer. You don't need to create instances of OssDefaultMemorySystem.

Definition

class OssDefaultMemorySystem {
public:
    OssDefaultMemorySystem();
public:
    virtual void *asn1_malloc(size_t size);
    virtual void asn1_free(void *ptr);
    virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size);
};

Methods

virtual void *asn1_malloc(size_t size);
Simply calls base_malloc.
virtual void asn1_free(void *ptr);
Simply calls base_free.
virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size);
Simply calls base_realloc.

OssPoolMemorySystem

The OssPoolMemorySystem class is the memory system that provides memory pools. To use memory pools, you simply create an instance of OssPoolMemorySystem in the beginning of the application, before creating additional threads:

OssPoolMemorySystem sys;

After the memory system has been created, you may start creating and using memory pool objects as needed.

When OssPoolMemorySystem is active, it tracks the usage of memory pools by different threads. If the current thread has some memory pool active, asn1Malloc uses this pool for memory allocation. If the current thread has no active memory pool, it uses simple malloc. The memory system also keeps track of the memory pool that an allocated memory block belongs to. When the block is deallocated, it is deallocated by the same memory pool it was allocated from, regardless of which pool is now active for the current thread. However, make sure that the memory pool belongs to the same thread!

NOTE: If you allocated an object in a memory pool, deallocate it in the same thread that allocated it.

Although OssPoolMemorySystem may be used without using memory pools, do not do so. You will get the same behavior as the default, except with larger memory requirements due to OssPoolMemorySystem's overhead.

Definition

class OssPoolMemorySystem {
public:
    OssPoolMemorySystem();
public:
    virtual void *asn1_malloc(size_t size);
    virtual void asn1_free(void *ptr);
    virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size);
};

Methods

virtual void *asn1_malloc(size_t size);
If some memory pool is active for the current thread, this method allocates a block from the memory pool, otherwise it calls base_malloc.
virtual void asn1_free(void *ptr);
If the block was allocated by a memory pool, this method invokes the memory pool's method to deallocate the block, otherwise it calls base_free.
virtual void *asn1_realloc(void *ptr, size_t oldsize, size_t size);
This method calls asn1_malloc() to allocate the new block, from the active memory pool if there is one, copies the data from the old block, and frees the old block using asn1_free().

OssMemoryPool

The OssMemoryPool class is an abstract class that is a common ancestor of all memory pool classes. Currently, there is only one concrete memory pool class – OssSimpleMemoryPool. Upcoming versions may contain other implementations of the OssMemoryPool interface.

Definition

class OssMemoryPool
{
public:
    THREADID get_thread();
    int activate();
    int deactivate();
    virtual void *alloc(size_t size) = 0;
    virtual void clean() = 0;
    virtual void dealloc(void *ptr) = 0;
    virtual ~OssMemoryPool();
};

Methods

THREADID get_thread();
Returns the identifier of the thread the pool belongs to. This function is used internally by the ASN.1/C++ Runtime code, but you may use it in your application if needed.
int activate();
Makes the pool active for the current thread. The pool should either be free (does not belong to any thread) or belong to the current thread. If it belongs to any other thread, the behavior of the function is determined by the concrete type of the pool. In particular, OssSimpleMemoryPool returns an error if the pool already belongs to any other thread. It returns 0 if the operation is successful and a non-zero error code otherwise (if an exception is not thrown from the error-handling function).
int deactivate();
Deactivates the pool. If the pool is not active, does nothing. If the pool is active but does not belong to the current thread, it returns an error. Otherwise, the pool is marked as not active and the current thread will have no active pool after the call, but the pool remains owned by the current thread. The function returns 0 if the operation is successful and a non-zero error code otherwise (if an exception is not thrown from the error-handling function).
virtual void *alloc(size_t size) = 0;
This is the function that allocates the small block from the pool's storage. If there is not enough free storage in the pool, the pool should request an additional big block from the system using the base_alloc method of the current memory system.
virtual void dealloc(void *ptr) = 0;
This is the function that returns a small memory block to the pool's storage.
virtual void clean() = 0;
This function marks all the memory storage as free and allows its reuse by the alloc function. No destructors are called for any of the objects allocated from the pool. All the pointers pointing to the pool's storage simply become invalidated.

NOTE: Use the clean() function with caution. It is an error-prone operation since it bypasses the normal C++ manner of object destruction. You should make sure that you do not explicitly deallocate or call destructors for any of the objects that were allocated from the pool, otherwise you will free already freed memory. This can result in memory corruption.
virtual ~OssMemoryPool();
The destructor. This function returns all the pool's storage to the system, using the base_free method of the current memory system. If there are any objects in the pool at the time of the pool's destruction, all the pointers to them become invalid. You should make sure that you do not explicitly deallocate or call destructors for any of those objects.

OssSimpleMemoryPool

The OssSimpleMemoryPool class is an concrete memory pool class that is tied to a single thread during its lifecycle. Currently, it is the only concrete memory pool class, but upcoming versions may contain other implementations of the OssMemoryPool interface.

Definition

class OssSimpleMemoryPool
{
public:
    OssSimpleMemoryPool(size_t chunk_size);
    virtual void *alloc(size_t size);
    virtual void clean();
    virtual void dealloc(void *ptr);
};

Methods

OssSimpleMemoryPool(size_t chunk_size);
Creates the new memory pool, which does not yet belong to any thread. The chunk_size parameter determines the minimum amount of memory (in bytes) that the pool requests from the system. That is, if the existing pool's memory is exhausted and the needed amount of memory is less than chunk_size, the pool requests exactly chunk_size bytes. Once the system has returned some memory block (of chunk_size bytes), the pool allocates the needed amount of memory from it, and the rest is added to the pool's free memory. If the needed amount of memory is greater than chunk_size, the pool requests from the system exactly as much memory as needed.
virtual void *alloc(size_t size);
This function allocates the small block from the pool's storage. If there is not enough free storage in the pool, the pool requests an additional big block at least of the size of chunk_size bytes from the system, using the base_alloc method of the current memory system.
virtual void dealloc(void *ptr);
This function returns a small memory block to the pool's storage.
virtual void clean();
This function marks all the memory storage as free and allows its reuse by the alloc function. All the pointers pointing to the pool's storage become invalid. Note that the freed memory remains owned by the pool. A pool does not release any of its memory to the system except in its destructor.

NOTE: Use the clean() function with caution. It is an error-prone operation since it bypasses the normal C++ manner of object destruction. You should make sure that you do not explicitly deallocate or call destructors for any of the objects that were allocated from the pool, otherwise you will free already freed memory. This can result in memory corruption.

This documentation applies to release 6.5 and later of the OSS® ASN.1 Tools for C++.

Copyright © 2017 OSS Nokalva, Inc. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any means electronic, mechanical, photocopying, recording or otherwise, without the prior permission of OSS Nokalva, Inc.
Every distributed copy of the OSS® ASN.1 Tools for C++ is associated with a specific license and related unique license number. That license determines, among other things, what functions of the OSS ASN.1 Tools for C++ are available to you.