"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:
|
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
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: