"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):
Note: Using the dot operator on any of s1, s2, or s3 results in these error messages:// 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
g++:
clang++: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; ^~~~~
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:
Diagram (with arbitrary addresses):// 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 }
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:
Diagram (with arbitrary addresses):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!
Example 7: Zero-initialization
The reason that the empty parenthesesvoid 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 }
set everything to zero is rather complicated and has to do with constructors which we haven't seen yet.new int[10]();
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: