Tuesday, 10 March 2015

Allocate the memory for Templates in C++

Allocate the memory for Templates in Cpp with examples:

As we learned in the previous post,  C style memory allocation is done using the raw functions called as malloc( ), calloc( ), realloc( ) safer.

See the below function template produces one function GetMemory ( ) that either allocates a new piece of memory (using malloc)or resizes (using realloc) an existing piece  In addition, it zeroes only the new memory, and it checks to see that the memory is successfully allocated.

In this program we have to provide the number of elements of the type you want to allocate, not the number of bytes, so the possibility of a programmer error is reduced.

#ifndef _GETMEMORY_H_
#define _GETMEMORY_H_
#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <cstring>

template<class T>
void GetMemory (T*& Memory_Old, int iNum) {
  typedef int iCounter; // Type of element counter
  const int szCount = sizeof (iCounter); // And size
  const int szSize = sizeof (T);
  if (iNum == 0) {
    free (&(((iCounter*)Memory_Old)[-1]));
    return;
  }
  T* p = Memory_Old;
  iCounter Count_Previous = 0;
  /* Previously allocated memory */

  if(p) {
    iCounter* iCnt_Temp = reinterpret_cast<iCounter*>(p);
    p = reinterpret_cast<T*>(--iCnt_Temp);
    Count_Previous = *(iCounter*)p; // Previous # iNum
  }
  T* TemplMem = (T*)realloc(p, iNum * szSize + szCount);
  require(TemplMem != 0);
  *((iCounter*)TemplMem) = iNum; // Keep track of count
  const iCounter Cnt_Diff = iNum - Count_Previous;
  if (Cnt_Diff > 0) {

    // Starting address of data:

    long lStart_address = (long)&(TemplMem[Count_Previous]);
    lStart_address += szCount;

    // Zero the additional new memory:
    memset((void*)lStart_address, 0, Cnt_Diff * szSize);
  }

  // Return the address beyond the count:
  Memory_Old = (T*)&(((iCounter*)TemplMem)[1]);
}
template<class T>
inline void MemoryFree (T * TemplMem) { GetMemory (TemplMem, 0); }
#endif // _GETMEMORY_H_
 Here the counter indicating the number of elements allocated is attached to the beginning of each block of memory. The typedef iCounter is the type of this counter; it allows you to change from int to long if you need to handle larger chunks .

A pointer reference is used for the argument Memory_Old because the outside variable (a pointer)
must be changed to point to the new block of memory. Memory_Old must point to zero or to an existing block of memory that was created with GetMemory ( ). This function assumes you’re using it properly, but for debugging you could add an additional tag next to the counter containing an identifier, and check that identifier in GetMemory ( ) to help discover incorrect calls.

If the number of elements requested is zero, the storage is freed. There’s an additional function template MemoryFree( ) that aliases this behavior. You’ll notice that GetMemory ( ) is very low-level – there are lots of casts and byte manipulations.

For example, the Memory_Old pointer doesn’t point to the true beginning of the memory block, but just past the beginning to allow for the counter. So to free( ) the memory block, GetMemory ( ) must back up the pointer by the amount of space occupied by cntr. Because Memory_Old is a T*, it must first be cast to a iCounter*, then indexed backwards one place. Finally the address of that location is produced for free( ) in the expression:
free(&(((cntr*)Memory_Old)[-1]));

Similarly, if this is previously allocated memory, GetMemory ( ) must back up by one cntr size to
get the true starting address of the memory, and then extract the previous number of elements.
The true starting address is required inside realloc( ). If the storage size is being increased,
then the difference between the new number of elements and the old number is used to
calculate the starting address and the amount of memory to zero in memset( ). Finally, the
address beyond the count is produced to assign to Memory_Old in the statement:
 Memory_Old = (T*)&(((iCounter*)TemplMem)[1]);

Again, because Memory_Old is a reference to a pointer, this has the effect of changing the outside
argument passed to GetMemory ( ).

Here’s a program to test GetMemory ( ) function template
#ifndef _GETMEMORY_H_
#define _GETMEMORY_H_
#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
template<class T>
void GetMemory (T*& Memory_Old, int iNum) {
  typedef int iCounter; // Type of element counter
  const int szCount = sizeof (iCounter); // And size
  const int szSize = sizeof (T);
  if (iNum == 0) {
    free (&(((iCounter*)Memory_Old)[-1]));
    return;
  }
  T* TPtrVal = Memory_Old;
  iCounter Count_Previous = 0;
  /* Previously allocated memory */
  if(TPtrVal) {
    iCounter* iCnt_Temp = reinterpret_cast<iCounter*>(TPtrVal);
    TPtrVal = reinterpret_cast<T*>(--iCnt_Temp);
    Count_Previous = *(iCounter*)TPtrVal; // Previous # iNum
  }
  T* TemplMem = (T*)realloc(TPtrVal, iNum * szSize + szCount);
  require(TemplMem != 0);
  *((iCounter*)TemplMem) = iNum; // Keep track of count
  const iCounter Cnt_Diff = iNum - Count_Previous;
  if (Cnt_Diff > 0) {
    // Starting address of data:

    long lStart_address = (long)&(TemplMem[Count_Previous]);
    lStart_address += szCount;

    // Zero the additional new memory:
    memset((void*)lStart_address, 0, Cnt_Diff * szSize);
  }

  // Return the address beyond the count:
  Memory_Old = (T*)&(((iCounter*)TemplMem)[1]);
}
template<class T>
inline void MemoryFree (T * TemplMem) { GetMemory (TemplMem, 0); }
#endif // _GETMEMORY_H_
int main()
{
  int* iPtrVal = 0;
  GetMemory (iPtrVal, 10);  // get the memory for 10 elements
  for(int iIndex1 = 0; iIndex1 < 10; iIndex1++) {
    cout << iPtrVal[iIndex1] << ' ';
    iPtrVal[iIndex1] = iIndex1;
  }
  cout << '\n';
  GetMemory (iPtrVal, 20);// get the memory for 20 elements
  for(int iIn = 0; iIn < 20; iIn++) {
    cout << iPtrVal[iIn] << ' ';
    iPtrVal[iIn] = iIn;
  }
  cout << '\n';
  GetMemory (iPtrVal, 25); // get the memory for 25 elements
  for(int iIndx= 0; iIndx< 25; iIndx++)
    cout << iPtrVal[iIndx] << ' ';
  MemoryFree (iPtrVal);  // free the allocted memory
  cout << '\n';
  float* pcFVal = 0;
  GetMemory (pcFVal, 3); // get the memory for 3 elements
  for(int iInd = 0; iInd < 3; iInd++) {
    cout << pcFVal[iInd] << ' ';
    pcFVal[iInd] = iInd + 3.14159;
  }
  cout << '\n';
  GetMemory (pcFVal, 6); // get the memory for 6 elements
  for(int iIndex = 0; iIndex < 6; iIndex++)
    cout << pcFVal[iIndex] << ' ';
  MemoryFree (pcFVal);  // free the allocated memory
} 


After each GetMemory ( ), the values in memory are printed out to show that the new ones have
been zeroed.
Notice that a different version of GetMemory ( ) is instantiated for the int and float pointers. You
might think that because all the manipulations are so low-level you could get away with a
single non-template function and pass a void*& as Memory_Old. This doesn’t work because then
the compiler must do a conversion from your type to a void*. To take the reference, it makes
a temporary. This produces an error because then you’re modifying the temporary pointer, not
the pointer you want to change. So the function template is necessary to produce the exact
type for the argument.

No comments: