Introduction to new/delete

"The road to hell is paved with global variables" -- Steve McConnell

Dynamic Memory Allocation in C++

Comparing C and C++ memory allocating:


Example 1: Simple allocation of built-in type:
void f1()
{
    // Dynamically allocate space for an int
  int *i1 = (int *) malloc(sizeof(int)); // C and C++ (4 bytes)
  int *i2 = new int;                     // C++ only (4 bytes)

    // Use i1 and i2

    // Release the memory (programmer)
  free(i1);  // C and C++
  delete i2; // C++ only
}

Note: Unlike in C, casting the return from malloc is required in C++. It's an error to assign a void pointer to an int pointer.


Example 2: Allocation of a struct:

  // On-screen graphic
struct Sprite
{
  double x;      //  8 bytes
  double y;      //  8 bytes
  int weight;    //  4 bytes
  int level;     //  4 bytes
  char name[20]; // 20 bytes
};    

void f2()
{
  Sprite s1; // Allocated on the stack (handled by compiler)

    // Dynamically allocate on the heap (handled by the programmer)
  Sprite *s2 = (Sprite *) malloc(sizeof(Sprite)); // 44 bytes, ignoring padding/alignment (C and C++)
  Sprite *s3 = new Sprite;                        // 44 bytes, ignoring padding/alignment (C++ only)

  s1.level = 1;  // s1 is a Sprite struct
  s2->level = 2; // s2 is a pointer to a Sprite struct
  s3->level = 3; // s3 is a pointer to a Sprite struct

    // Other stuff ...

    // Release the memory (programmer)
  free(s2);  // C and C++
  delete s3; // C++ only

} // s1 goes out of scope and the memory is released automatically


Example 3: Allocating arrays of built-in types:

void f3()
{
    // Allocate space for array of 10 chars and 10 ints (C and C++)
  char *p1 = (char *) malloc(10 * sizeof(char)); // 10 bytes
  int *p2 = (int *) malloc(10 * sizeof(int));    // 40 bytes

    // Allocate space for array of 10 chars and 10 ints (C++ only)
  char *p3 = new char[10]; // 10 bytes
  int *p4 = new int[10];   // 40 bytes

    // Use p1, p2, p3, p4 ...

    // Release the memory (programmer)
  free(p1);     // C and C++
  free(p2);     // C and C++
  delete [] p3; // C++ only (array delete)
  delete [] p4; // C++ only (array delete)
}

Note: If you allocate an array with new (using the array operator, []), you MUST use delete with the array operator as well. Failure to do so causes your program to be undefined (and lose points on an assignment) EVEN IF YOU GET THE CORRECT OUTPUT.


Example 4: Allocating arrays of structs (user-defined type):

  // On-screen graphic
struct Sprite
{
  double x;      //  8 bytes
  double y;      //  8 bytes
  int weight;    //  4 bytes
  int level;     //  4 bytes
  char name[20]; // 20 bytes
};    
	
void f4()
{
    // Allocated array of 10 Sprites on the stack (handled by compiler)
  Sprite s1[10]; 

    // Dynamically allocate array of 10 Sprites on the heap (handled by the programmer)
  Sprite *s2 = (Sprite *) malloc(10 * sizeof(Sprite)); // 440 bytes, ignoring padding/alignment (C and C++)
  Sprite *s3 = new Sprite[10];                         // 440 bytes, ignoring padding/alignment (C++ only)

  s1[0].level = 1; // s1[0] is a Sprite struct
  s2[0].level = 2; // s2[0] is a Sprite struct
  s3[0].level = 3; // s3[0] is a Sprite struct

  s1->level = 3; // Does this work? Yes. s1 decays into a pointer to the first element.
  s2->level = 4; // Does this work? s2 and s3 are pointers to the first element. All of these
  s3->level = 5; // Does this work? modify the first element in the "array".

    // Other stuff ...

    // Release the memory (programmer)
  free(s2);     // C and C++
  delete [] s3; // C++ only (array delete)

} // s1 goes out of scope and the memory is released automatically
Note: Using the dot operator on any of s1, s2, or s3 results in these error messages:

g++:

error: request for member 'level' in 's1', which is of non-class type 'Sprite [10]'
   s1.level = 5;
      ^~~~~
error: request for member 'level' in 's2', which is of pointer type 'Sprite*' (maybe you meant to use '->' ?)
   s2.level = 5;
      ^~~~~
clang++:
error: member reference base type 'Sprite [10]' is not a structure or union
  s1.level = 5;
  ~~^~~~~~
error: member reference type 'Sprite *' is a pointer; did you mean to use '->'?
  s2.level = 5;
  ~~^
    ->
2 errors generated.


Example 5: Modified Sprite struct:

  // On-screen graphic, new version
struct Sprite2
{
  double x;   // 8 bytes
  double y;   // 8 bytes
  int weight; // 4 bytes  
  int level;  // 4 bytes
  char *name; // 4 or 8 bytes (pointer, not an array)
};    

void f5()
{
    // Dynamically allocate Sprite2 on the heap (handled by the programmer)
  Sprite2 *s1 = (Sprite2 *) malloc(sizeof(Sprite)); // 28/32 bytes, ignoring padding/alignment (C and C++)
  Sprite2 *s2 = new Sprite2;                        // 28/32 bytes, ignoring padding/alignment (C++ only)

    // Dynamically allocate 10 chars on the heap (programmer)
  s1->name = (char *) malloc(10 * sizeof(char)); // 10 bytes (C and C++)
  s2->name = new char[10];                       // 10 bytes (C++ only)

    // Use s1 and s2 ...

    // Release memory for chars
  free(s1->name);     // C and C++
  delete [] s2->name; // C++ only (array delete)
  
    // Release memory for Sprite2
  free(s1);  // C and C++
  delete s2; // C++ only
}
Diagram (with arbitrary addresses):
The diagram only shows s2 and its associated memory but the diagram for s1 and its associated memory would be very similar.

Notice that you must free/delete the memory in the reverse order that they were allocated. You first allocate the structure, and then you allocate the name. When you want to release everything, you first release the name and then release the structure. If you released the structure first, you will have no way of releasing the name (the pointer is gone).


Example 6: Stack and heap allocations:

void f6()
{
    // Allocate 10 characters on the stack
  char a[10]; 

    // Point at the first element (C and C++)
  char *p1 = a;

    // Allocate space for array of 10 chars and 10 ints (C and C++)
  char *p2 = (char *) malloc(10 * sizeof(char)); // 10 bytes

    // Allocate space for array of 10 chars and 10 ints (C++ only)
  char *p3 = new char[10]; // 10 bytes

    // Use p1, p2, p3
    // All three pointers work in the exact same way
    // There is no way to tell how the memory was allocated

    // Release the memory
  free(p2);     // C and C++
  delete [] p3; // C++ only

} // a is automatically released here
  // calling free on a or p1 is very dangerous!
Diagram (with arbitrary addresses):


Example 7: Zero-initialization

void f7()
{
  int *p1 = (int *) calloc(10, sizeof(int)); // 40 bytes, ignoring padding/alignment, all zero, C/C++
  int *p2 = new int[10]();                   // 40 bytes, ignoring padding/alignment, all zero, C++ only

  // use the memory...

  free(p1);     // C and C++
  delete [] p2; // C++ only
}
The reason that the empty parentheses
new int[10]();
set everything to zero is rather complicated and has to do with constructors which we haven't seen yet.

The empty parentheses causes every element to be set to its zero value, whatever that is. For integers, it is 0, for doubles it is 0.0, for pointers, it is NULL, etc.


Notes: