Local variables exist only as long as the block that created them. For a value to persist beyond that point, or for the case where the number of objects cannot be known until runtime, the language offers a mechanism: dynamic allocation, using new and delete.
The new expression asks storage to be allocated from the heap, constructs an object in the storage, and then returns the address of the object. The address is normally stored in a pointer so that the program can access the object at a later time.
#include <iostream>
int main()
{
// Allocate this object dynamically
int* ptr { new int{ 42 } };
std::cout << *ptr << '\n';
delete ptr;
return 0;
}
In the example above, new int{ 42 } reserves space to store one integer, puts 42 in there, and returns the address of that piece of space. The pointer ptr is assigned that address. The line below then takes the address and the value pointed to by that address and prints it.
When that value is no longer needed, the line to the right is called to invoke destruction of the pointed to object and return the memory to the heap. At this point, the address stored in ptr is invalid.
If delete is not used, the memory will stay allocated for the lifetime of the program. The pointer may go out of scope, but the storage it pointed to will never be deallocated. This unreclaimed memory is called a memory leak.
#include <iostream>
int main()
{
int* ptr { new int{ 7 } };
// delete was forgotten
return 0;
}
In this program, the integer is allocated and then discarded. The memory remains allocated until the program terminates. In a long running program, many such omissions can use up the available memory.
After delete has been invoked, the pointer still contains the address in numeric form, but that address isn't for a living object anymore. Dereferencing the pointer after delete is undefined behavior.
#include <iostream>
int main()
{
int* ptr { new int{ 99 } };
delete ptr;
// std::cout << *ptr << '\n'; // Undefined behavior
return 0;
}
That is a commented line trying to access freed memory. The language places no restrictions on behavior; it may crash, display some junk value, or seem to function, until some later modification reveals the bug.
The customary protection is to set the pointer to null immediately after the delete. A null pointer can be tested before trying to use the pointer again.
#include <iostream>
int main()
{
int* ptr { new int{ 99 } };
delete ptr;
// Use nullptr when the pointer refers to no object
ptr = nullptr;
if (ptr)
{
std::cout << *ptr << '\n';
}
return 0;
}
Once the ptr = nullptr line is executed, the test if (ptr) evaluates to false and the dereference isn't attempted. It is safe to delete a null pointer as the deletion operation is defined to do nothing. This allows the same pointer to be safely deleted more than once if it has been assigned to null.
The other advantage of dynamic allocation is that the program itself can make the determination as to how much space to allocate to an object at run time. When a single int is not enough, new can construct an array of size only known at run time.
#include <iostream>
int main()
{
int length { 3 };
int* arr { new int[length]{ 10, 20, 30 } };
std::cout << arr[0] << '\n';
std::cout << arr[1] << '\n';
std::cout << arr[2] << '\n';
// Release the dynamically allocated array
delete[] arr;
return 0;
}
For example, new int[length]{ 10, 20, 30 } creates space for three new integers and initializes each one. The arr pointer contains a value, which points to the first element. Accessing the elements is the same as for ordinary arrays, using the so called subscript notation.
When the array is no longer needed, the delete[] arr form should be used; the square brackets instruct the language to destroy every element of the array and free the entire block.
Using delete whenever the array has been allocated with new[] will create undefined behavior. The reverse mismatch is also undefined.
The basic contract is easy: for every triumphant new, there should be a corresponding call to delete or delete[], with the pointer to the allocated memory never accessed after its memory has been freed. When that contract is upheld, the program is free to churn out objects, which persist past the end of a block; if it isn't, the sinkholes can include leaks, dangling pointers, or unspecified behavior.
Exercise: Dynamic Allocation with new and deleteStarter code, hints and solutionOpenClose
Practice: Dynamic Allocation with new and delete
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Print the allocated 99 before delete and report that the pointer is null afterward. Explain the matching allocation and release and avoid any post-release dereference.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
int main()
{
int* ptr { new int{ 99 } };
delete ptr;
// Use nullptr when the pointer refers to no object
ptr = nullptr;
if (ptr)
{
std::cout << *ptr << '\n';
}
return 0;
}Hints
new int{99} allocates and initializes one int. Match it with the single-object form delete ptr, not delete[] ptr.
The starter frees the int before its guarded print. To inspect a different initial value, print it while the allocation is still alive, before delete.
Worked solution
The solution observes 99 before its delete, then verifies that ptr was cleared. new int constructs one int, so single-object delete is the matching release; nothing is dereferenced afterward.
#include <iostream>
int main()
{
int* ptr { new int{ 99 } };
std::cout << "Allocated value: " << *ptr << '\n';
delete ptr;
// Use nullptr when the pointer refers to no object
ptr = nullptr;
if (ptr)
{
std::cout << *ptr << '\n';
}
std::cout << "Released and cleared: " << (ptr == nullptr) << '\n';
return 0;
}
Example output
Allocated value: 99
Released and cleared: 1
0 comments
No comments yet. Be the first.