Dynamic memory allocation

Differences between static and dynamic memory allocation:

Basically, use dynamic memory allocation only if want an object to live beyond the lifetime of the scope when create it in. For example, function return the pointer.


/* The C way to request dynamic memory from the heap */
int *i = (int*) malloc(sizeof *i);
double *d = (double*) malloc(sizeof(double) * NUM);
free(d);
free(i);

// The C++ way
int *i = new int(0);
double *d = new double[NUM];
delete []d;
delete i;
Advantages:

Don’t mix new/delete with malloc/free. The result is undefined.


C++ implementation cannot effectively protect itself against repeated delete. One reason is that the operand of delete need not be an lvalue.
T* p = new T;
T* q = p;
delete p;
delete q; // error!

delete p+1;
delete f(x);
If zeroing out pointer is important, consider using a destroy function. Passing the pointer as a reference (to allow the pointer to be zero'd out) has the added benefit of preventing destroy() from being called for an rvalue.
template<class T> inline void destroy(T*& p) { delete p; p = NULL; }

// error: trying to pass an rvalue by non-const reference
destroy(f());
destroy(p+1);

In ANSI C++, when new cannot satisfy a request for memory, it calls the new-handler function. Not once, but repeatedly until it can find enough memory. If the new-handler is NULL (as by default), it will throw std::bad_alloc exception. It is a lot more attractive than wrapping every use of new inside try block, so it is common to use this way.

But Visual C++ 6.0 is not compatible with ANSI C++ in this case. Its <setnewh.cpp> has suggested not to use standard set_new_handler defined in &lr;new> but use the _set_new_handler defined in new.h. It use its own way of memory allocation error handling. So conceal the platform difference by
#ifdef _MSC_VER
  // New handler for Microsoft Visual C++ compiler
  #include <new.h>

  int __cdecl newHandler(size_t size)
  {
    // Do whatever
    return 0;
  } 
#else
  // Ansi C++ new handler
  #include <new>
  using namespace std;

  void newHandler(void)
  {
    // Do whatever
  }
#endif
   
void SetNewHandler(void)
{
  #ifdef _MSC_VER
    _set_new_handler(newHandler);
    _set_new_mode(1); // Re-route malloc failures to new handler!
  #else
    set_new_handler(newHandler);
  #endif
}
User-defined new-handler should:
#include <new>
#include <iostream>
#include <cassert> // for debug version, not production code
using namespace std;

void NoMoreMemory(void)
{
  cerr << "out of memory";
  abort();
}

void main(void)
{
  set_new_handler(NoMoreMemory);

  int *pBigDataArray = new int[100000000000000]; // ok. well, so BIG!
  assert(pBigDataArray);
  cout << "new int[100000000000000] successfully" << endl;
  pBigDataArray[0] = 1;
  pBigDataArray[99999999999999] = 9;
  cout << pBigDataArray[0] << endl;   // 1
  cout << pBigDataArray[99999999999999] << endl;   // 9

  delete [] pBigDataArray;

  pBigDataArray = new int[100000000000000000000];
  assert(pBigDataArray);
  cout << "new int[100000000000000000000] successfully" << endl;
  cout << "terminate normally";

  delete [] pBigDataArray;
  return 0;
}

When test in Linux GCC, there is compile error when try to allocate more than 536870911 int and it also cause runtime error (trigger _newHandler). Since int is 4 bytes, total memory allowed to allocate is 2147483644 bytes, or >2GB (2147483648 bytes). Also, #include <new> is optional.

For class-specific new-handler
#include <new> // new_handler function typedef already defined
using namespace std;

template<class T>
class NewHandlerSupport {
public:
  static new_handler SetNewHandler(new_handler p);
  static void* operator new(size_t size, new_handler p=NULL);
private:
  static new_handler currentHandler;
};

// in implementation file
template<class T>
new_handler NewHandlerSupport::currentHandler; // set to 0/NULL by default

template<class T>
new_handler NewHandlerSupport::SetNewHandler(new_handler p)
{
  new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}

template<class T>
void* NewHandlerSupport::operator new(size_t size)
{
  // prevent derived class call base class new, also ensure to return
  // legitimate pointer even 0 byte requested as sizeof a
  // freestanding class never 0
  if (size != sizeof NewHandlerSupport)
    return ::operator new(size);

  new_handler globalHandler = set_new_handler(currentHandler);
  void *memory;
  try {
    // call the global new, which may invoke currentHandler
    memory = ::operator new(size);
  } catch (bad_alloc&) { // if ultimately fail to resolve
    set_new_handler(globalHandler); // restore handler
    throw;
  }
  set_new_handler(globalHandler); // restore handler

  return memory;
}

//client
class ClassName : public NewHandlerSupport {
  // ...
};

// usage in application
void NoMoreMemory1(void);
void NoMoreMemory2(void);
ClassName::SetNewHandler(NoMoreMemory1);
ClassName *pc1 = new ClassName; // call NoMoreMemory1 if fail
string *ps = new string; // call global new-handler if there is one
ClassName *pc1 = new (NoMoreMemory2) ClassName; // call NoMoreMemory2 if fail
ClassName::set_new_handler(NULL);
ClassName *pc2 = new ClassName; // throw bad_alloc

operator new is that it's inherited by subclasses


To trace memory leak in Visual C++:
  1. Build in Debug version and run it (F5). By defualt memory leak detection is on and so it will dump all objects that have not been deallocated.
    Detected memory leaks!
    Dumping objects ->
    {685405} normal block at 0x09908790, 88 bytes long.
     Data: <<       ,       > 3C 80 90 09 AC 8D 90 09 2C 8A 90 09 80 01 00 00 
    strcore.cpp(118) : {544} normal block at 0x00B05690, 28 bytes long.
     Data: <            WL_S> 9C 01 00 00 0F 00 00 00 0F 00 00 00 57 4C 5F 53 
    {540} normal block at 0x00B056E0, 44 bytes long.
     Data: <    0X  0V  LW  > A0 12 B0 00 30 58 B0 00 30 56 B0 00 4C 57 B0 00 
    Object dump complete.
    
  2. The allocation request number is enclosed in curly brackets (for example, {540}). The numbers in braces at the beginning of most lines specify the order in which the objects were allocated. The most recently allocated object appears first. Not all are memory leak. The allocations associated with frame variables are automatically deallocated when the frame variable goes out of scope. Only heap allocation requires manual deallocation.
  3. Once know the allocation request number of an improperly allocated block, pass this number to create a breakpoint. To set a breakpoint when a particular allocation occurs, first set a breakpoint at the beginning of main and run in the debugger(F5). Add the static global variable (_crtBreakAlloc or _afxBreakAlloc) in the Watch, by default its value is -1. Set it to the allocation request number that is interested in (use the smallest number first so can trace down). Press F5 to continue. Repeat this for each allocation request number.
  4. Execution will break just prior to allocating the block. Use Call Stack to backtrack what routine was responsible for the bad call.

Visual C++ Debug version provide other features to detect and check for memory overwrite. For more information look up in MSDN.

If the code has new operations, delete operations and pointer arithmetic all over the place, it will going to mess up somewhere and get memory leaks, stray pointers, etc. It follows that successful techniques rely on hiding allocation and deallocation inside more manageable types.
More Effective C++ p.59 注2 说:

『1995 七月 ISO/ANSI C++ 标准委员会加入一个新函式:uncaught_exception。
如果一个 exception 正作用中而尚未被捕捉的话,它会传回 true。』

C++ Standard (ISO/IEC 14882) 18.6.4 :

bool uncaught_exception();

returns : true after completing evaluation of a throw-exception until
either completing initialization of the exception-declaration in the
matching handler or entering unexpected() due to the throw; or after
entering terminate() for any reason other than an explicit call to
terminate(). Note: This includes stack unwinding.

现在我写一个程式测试之。BCB4 和 GCC 有良好表现。VC6 表现不佳。
显然,你说「uncaught_exception 总是传回 false」并不正确。

#0001 #include <iostream>
#0002 #include <exception> // only gcc need this file
#0003 using namespace std;
#0004
#0005 class myexception { };
#0006
#0007 class mytest
#0008 {
#0009 public:
#0010   ~mytest() {
#0011   cout << uncaught_exception() << endl;
#0012   // 如果此时有 exception 作用中且未被捕捉,会输出 1.
#0013   }
#0014 };
#0015
#0016 int main()
#0017 {
#0018   try {
#0019     cout << uncaught_exception() << endl;
#0020     // 0. 是的,因为未有任何 exception 处於作用状态。
#0021
#0022     throw myexception();
#0023   }
#0024   catch (myexception)
#0025   {
#0026     cout << uncaught_exception() << endl;
#0027     // 0. 是的,因为未有任何 exception 未被捕捉。
#0028   }
#0029
#0030   mytest t1;
#0031   throw myexception();
#0032   // VC6 执行结果:none abnormal program termination (不佳)
#0033   // CB4 执行结果:1 Abnormal program termination (佳)
#0034   // GCC 执行结果:1 (佳)
#0035 }
Index