C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
45: new and delete
A few years ago, I was reviewing code for a junior dev who had built a fancy particle system for a small indie game. The visuals were stunning, but there was a problem: the game would run perfectly for about twenty minutes, and then the frame rate would tank until the entire OS froze. When we looked at the task manager, the application was eating 12GB of RAM and still climbing. He was calling new Particle() every time a spark flew from an explosion, but he'd forgotten to ever call delete. He had created a "memory leak"—a slow-motion train wreck where the program claims memory from the system but never gives it back, effectively starving every other process on the machine.
Claiming Your Slice of the Heap
Up until now, we've mostly dealt with automatic memory. When you declare int x = 10; inside a function, that variable lives on the stack. It's fast, and it disappears the moment the function returns. But the stack is limited, and more importantly, you can't decide at runtime how much space you need. If you don't know if your user is going to upload ten images or ten thousand, the stack won't cut it.
This is where new comes in. When you use new, you're asking the operating system for a chunk of memory on the "heap." The heap is a massive pool of memory available to your program. Because the system doesn't know when you're finished with this memory, new doesn't return a value; it returns a pointer to the address of the allocated space. I always tell people to think of new as a contract: the system gives you the memory, but you are now legally responsible for it until the end of time (or until the program terminates).
int* myDynamicInt = new int; // Allocate one integer on the heap
*myDynamicInt = 42; // Use it like any other pointer
The Obligation of the Delete Operator
The contract I mentioned earlier is where most C++ bugs are born. Unlike Java or Python, C++ has no garbage collector. There is no "cleanup crew" coming behind you to pick up the trash. If you call new, you must eventually call delete. If you lose the pointer to that memory before you delete it—say, by letting the pointer variable go out of scope—that memory is "leaked." It's still reserved by your program, but you no longer have the address to find it or free it.
To fulfill your end of the bargain, you use the delete operator. This tells the system, "I'm done with the memory at this address; feel free to give it to someone else."
delete myDynamicInt; // Free the memory
myDynamicInt = nullptr; // Good practice: prevent "dangling pointers"
I highly recommend setting your pointer to nullptr immediately after deleting it. There is nothing more frustrating than a "use-after-free" bug, where you accidentally try to read memory you've already given back to the OS, leading to crashes that are incredibly hard to debug.
The Danger of the Square Bracket Mismatch
Things get slightly more complex when you need to allocate an array. If you need a block of memory for multiple items, you use new[]. This is common when you're implementing your own data structures or handling raw binary buffers.
int size = 100;
int* myArray = new int[size]; // Allocate an array of 100 integers
Here is the part that trips up almost everyone at first: you cannot use the standard delete to clean this up. You must use delete[]. If you use the single-object delete on an array, you're invoking undefined behavior. On some systems, it might work; on others, it will crash instantly; and on some, it will only delete the first element of the array and leak the rest. Always match your brackets: new pairs with delete, and new[] pairs with delete[].
delete[] myArray; // Correctly frees the entire array
📋 Practical Task
Exercise: Building a Dynamic String Buffer
Your task is to simulate a simple text buffer that can grow based on user input. Since we are focusing on new and delete, you will implement this manually without using std::string or std::vector.
Write a program that does the following:
- Asks the user how many characters they wish to store in a message.
- Uses
new char[]to allocate exactly that amount of memory (plus one extra byte for the null terminator'\0'). - Uses
std::cin.getline()to read the user's message into that buffer. - Prints the message back to the user.
- Correctly frees the memory using
delete[]before the program exits.
Bonus challenge: Wrap this logic in a way that ensures memory is deleted even if the user enters an invalid size (like a negative number).
There are no comments for now.