Mastering C++ Memory: When to Use new/delete vs malloc/free
This article explains the fundamental differences between C's malloc/free and C++'s new/delete, covering their underlying mechanisms, proper usage patterns, common pitfalls, error‑handling strategies, and best‑practice recommendations such as smart pointers and memory‑pool techniques.
1. Exploring malloc and free
1.1 Basic usage
In C, malloc allocates a raw memory block and returns a void* that must be cast to the desired pointer type; free releases the block back to the system.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed
");
return 1;
}
*ptr = 10;
printf("Value in allocated memory: %d
", *ptr);
free(ptr);
return 0;
}1.2 Underlying mechanism
On Linux, malloc is implemented by glibc, which first tries to satisfy requests from an internal memory pool; if the pool lacks space it falls back to system calls brk (for small allocations) or mmap (for large allocations).
1.3 Pitfalls and common traps
Always check the return value of malloc for NULL, set freed pointers to NULL to avoid dangling references, avoid memory leaks by freeing every allocation, and never free the same block twice.
int* ptr = (int*)malloc(100 * sizeof(int));
// Forgetting NULL check leads to crash if allocation fails
*ptr = 10;
int* p = (int*)malloc(sizeof(int));
free(p);
// Using p after free is undefined behavior
*p = 20;
void leak() {
int* p = (int*)malloc(sizeof(int));
// missing free -> memory leak
}
int* q = (int*)malloc(sizeof(int));
free(q);
free(q); // double free – undefined behavior2. The unique power of new and delete
2.1 Basic operation
In C++, new allocates memory (usually via operator new) and then automatically calls the object's constructor; delete calls the destructor before releasing the memory via operator delete.
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
};
int main() {
MyClass* obj = new MyClass;
delete obj;
return 0;
}2.2 Construction and destruction
When new Person("Alice", 25) is executed, memory is allocated, then the Person constructor initializes name and age. Deleting the object first runs the destructor, releasing any resources, then frees the memory.
class Person {
public:
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {
std::cout << "Constructor: " << name << ", " << age << std::endl;
}
~Person() {
std::cout << "Destructor: " << name << ", " << age << std::endl;
}
};2.3 Different forms of new
Ordinary new throws std::bad_alloc on failure; new (std::nothrow) returns NULL instead; placement new constructs an object in pre‑allocated memory.
// ordinary new with exception handling
try {
int* p = new int(10);
delete p;
} catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << std::endl;
}
// nothrow new
int* p = new (std::nothrow) int(10);
if (!p) { std::cerr << "Allocation failed" << std::endl; }
else { delete p; }
// placement new
char buffer[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass; // constructs in buffer
obj->~MyClass(); // manual destruction, no free needed3. Deep comparison of the two approaches
3.1 Allocation method
mallocrequires manual size calculation and returns a generic pointer; new deduces the size from the type and returns a correctly typed pointer, making code safer.
3.2 Initialization and cleanup
mallocleaves memory uninitialized; you must manually set values. new runs constructors for objects and zero‑initializes fundamental types when appropriate. free never calls destructors, while delete always does.
3.3 Error handling
mallocsignals failure by returning NULL; the programmer must check it. new signals failure by throwing std::bad_alloc, which can be caught with C++ exception handling.
3.4 Memory release and destructors
freesimply returns the block to the system; no destructors are run, so resources owned by objects are not released. delete first runs the destructor, then frees the memory, preventing leaks for class types.
4. Practical application scenarios
4.1 C projects
Dynamic allocation of an array of Student structures using malloc and releasing it with free demonstrates typical C‑style memory management.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float score;
} Student;
int main() {
int n;
printf("Enter number of students: ");
scanf("%d", &n);
Student* students = (Student*)malloc(n * sizeof(Student));
if (!students) { printf("Allocation failed
"); return 1; }
// fill and use students ...
free(students);
return 0;
}4.2 C++ projects
Creating a Character object with new automatically invokes its constructor; deleting it runs the destructor, which is ideal for game objects and other complex resources.
#include <iostream>
#include <string>
class Character {
public:
std::string name;
int health;
int attackPower;
Character(const std::string& n, int h, int a) : name(n), health(h), attackPower(a) {
std::cout << "Character " << name << " created" << std::endl;
}
~Character() { std::cout << "Character " << name << " destroyed" << std::endl; }
void move() { std::cout << name << " moves" << std::endl; }
void attack() { std::cout << name << " attacks with power " << attackPower << std::endl; }
};
int main() {
Character* player = new Character("Hero", 100, 20);
player->move();
player->attack();
delete player;
return 0;
}4.3 Mixed C/C++ usage
When a C function returns memory allocated with malloc, C++ code must free it with free, not delete, to avoid undefined behavior.
#include <stdio.h>
#include <stdlib.h>
char* c_function() {
char* str = (char*)malloc(100);
if (str) strcpy(str, "Hello, World!");
return str;
}
// C++ side
#include <iostream>
extern "C" char* c_function();
int main() {
char* s = c_function();
if (s) {
std::cout << s << std::endl;
free(s);
}
return 0;
}5. Best practices and recommendations
5.1 Choosing in C++
Prefer new / delete for objects because they handle construction and destruction automatically, which is essential for resource‑heavy classes such as textures or file handles.
5.2 Smart pointers
Modern C++ (C++11+) provides std::unique_ptr, std::shared_ptr, and std::weak_ptr to manage lifetimes automatically and eliminate most manual new/delete usage.
#include <memory>
#include <fstream>
class Logger {
public:
void log(const std::string& msg) { logFile << msg << std::endl; }
~Logger() { logFile.close(); }
private:
std::ofstream logFile;
};
class ResourceManager {
public:
std::unique_ptr<Logger> logger = std::make_unique<Logger>();
};5.3 Additional advice
Always pair allocation and deallocation functions correctly, check for allocation failures, consider memory pools for high‑frequency small allocations, and use tools like Valgrind or AddressSanitizer to detect leaks, dangling pointers, and out‑of‑bounds accesses.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
