Why new/delete Beats malloc/free: A Deep Dive into C/C++ Memory Management
This article explains the core differences between C's malloc/free and C++'s new/delete, covering allocation mechanisms, object construction and destruction, error handling, common pitfalls, and best practices—including smart pointers—to help developers choose the right memory management approach for various programming scenarios.
In C and C++ memory management, new/delete and malloc/free are two core toolsets with fundamental differences. malloc / free are C library functions that only allocate and release raw memory, returning void* that must be cast manually. new / delete are C++ operators that first call operator new (often using malloc) and then automatically invoke constructors and destructors, providing object‑level management.
The difference stems from language design: C is procedural and deals with memory blocks, while C++ is object‑oriented and must manage object lifetimes, including initialization and resource cleanup. new / delete can be overloaded to customize allocation strategies, whereas malloc / free have fixed behavior.
1. Enter the World of malloc and free
1.1 Basic Usage
In C, malloc allocates a block of memory of the requested size and returns a pointer to its beginning. If allocation fails, it returns NULL.
#include <stdio.h>
#include <stdlib.h>
int main() {
// Allocate memory for one int
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;
}The code allocates space for an int, checks for NULL, uses the memory, prints the value, and finally releases it with free.
1.2 Underlying Principle
On Linux, malloc is usually implemented by glibc, which maintains a memory pool. If the pool has enough free space, malloc satisfies the request without a system call, reducing overhead. When the pool lacks space, malloc falls back to system calls brk (to extend the heap) or mmap (to map a new region), depending on the requested size.
1.3 Usage Notes and Common Traps
Key pitfalls include forgetting to check the return value, using a pointer after free, and double‑freeing. Examples:
int* ptr = (int*)malloc(100 * sizeof(int));
// Forgot to check ptr
*ptr = 10; // Crash if malloc failed int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
free(ptr);
// ptr not set to NULL, dangling pointer
*ptr = 20; // Undefined behavior void memory_leak_example() {
int* ptr = (int*)malloc(sizeof(int));
// Missing free -> memory leak
} int* ptr = (int*)malloc(sizeof(int));
free(ptr);
free(ptr); // Double free – undefined behavior2. The Unique Charm of new and delete
2.1 Basic Operation Demonstration
In C++, new allocates memory and calls the object's constructor; delete calls the destructor and then releases the memory.
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor called" << std::endl; }
~MyClass() { std::cout << "MyClass destructor called" << std::endl; }
};
int main() {
MyClass* obj = new MyClass; // Constructor runs
delete obj; // Destructor runs, then memory freed
return 0;
}Unlike malloc, new automatically handles construction and destruction, making it safer for objects.
2.2 Construction and Destruction Behind the Scenes
newfirst calls operator new to allocate raw memory, then invokes the constructor to initialize the object. For example:
class Person {
public:
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {
std::cout << "Person constructor called, name: " << name << ", age: " << age << std::endl;
}
~Person() {
std::cout << "Person destructor called, name: " << name << ", age: " << age << std::endl;
}
};Creating an object with new Person("Alice", 25) allocates memory, runs the constructor, and prints the construction message. Deleting the object runs the destructor first, then frees the memory.
2.3 Various Forms of new
Three common variants:
(1) Normal new – throws std::bad_alloc on failure.
try {
int* num = new int(10);
delete num;
} catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << std::endl;
}(2) nothrow new – returns NULL instead of throwing.
#include <new>
int* num = new (std::nothrow) int(10);
if (num == NULL) {
std::cerr << "Allocation failed" << std::endl;
} else {
delete num;
}(3) Placement new – constructs an object in pre‑allocated memory.
#include <new>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor called" << std::endl; }
~MyClass() { std::cout << "MyClass destructor called" << std::endl; }
};
int main() {
char buffer[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass; // Construct in buffer
obj->~MyClass(); // Manually call destructor
return 0;
}3. Deep Comparison
3.1 Allocation Method
mallocrequires manual size calculation and returns a void* that must be cast. Example: int* arr = (int*)malloc(10 * sizeof(int));. new automatically computes the size and returns a correctly typed pointer: int* arr = new int[10];.
3.2 Initialization and Cleanup
mallocdoes not initialize memory; the contents are indeterminate. To zero‑initialize, you must call memset:
int* arr = (int*)malloc(10 * sizeof(int));
if (arr != NULL) {
memset(arr, 0, 10 * sizeof(int));
} freemerely releases the block without invoking destructors, which can lead to resource leaks for objects. new calls constructors (initializing built‑in types to default values and running user‑defined constructors) and delete calls destructors before freeing memory.
class MyClass {
public:
int data;
MyClass() : data(0) { std::cout << "MyClass constructor called, data=0" << std::endl; }
~MyClass() { std::cout << "MyClass destructor called" << std::endl; }
};
MyClass* obj = new MyClass; // Constructor runs
delete obj; // Destructor runs, then memory freed3.3 Error Handling Mechanism
mallocreturns NULL on failure, requiring explicit checks:
int* ptr = (int*)malloc(1000 * sizeof(int));
if (ptr == NULL) {
std::cerr << "Allocation failed" << std::endl;
} newthrows std::bad_alloc, which can be caught with a try‑catch block:
try {
int* ptr = new int[1000000000];
// use ptr
} catch (const std::bad_alloc& e) {
std::cerr << "Allocation failed: " << e.what() << std::endl;
}3.4 Memory Release Key: Destructor Invocation
freedoes not call destructors, so resources owned by objects are not released. Example with a Person object allocated via malloc:
Person* p1 = (Person*)malloc(sizeof(Person));
// No constructor called, memory only allocated
free(p1); // Destructor not called – potential leakUsing new and delete ensures the destructor runs:
Person* p2 = new Person("Bob", 30);
delete p2; // Destructor runs, then memory freed4. Real‑World Application Scenarios
4.1 C Language Project
Dynamic student management using malloc / free:
#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 == NULL) {
printf("Memory allocation failed
");
return 1;
}
for (int i = 0; i < n; i++) {
printf("Enter name for student %d: ", i+1);
scanf("%s", students[i].name);
printf("Enter age for student %d: ", i+1);
scanf("%d", &students[i].age);
printf("Enter score for student %d: ", i+1);
scanf("%f", &students[i].score);
}
printf("
Student information:
");
for (int i = 0; i < n; i++) {
printf("Name: %s, Age: %d, Score: %.2f
", students[i].name, students[i].age, students[i].score);
}
free(students);
return 0;
}4.2 C++ Project
Game‑style character management using new / delete:
#include <iostream>
#include <string>
class Character {
public:
std::string name;
int health;
int attackPower;
Character(const std::string& n, int h, int ap) : name(n), health(h), attackPower(ap) {
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 Usage
When C and C++ code coexist, memory allocated with malloc must be freed with free, and memory allocated with new must be deleted with delete:
#include <stdlib.h>
char* c_function() {
char* str = (char*)malloc(100 * sizeof(char));
if (str != NULL) {
const char* temp = "Hello, World!";
int i = 0;
while (temp[i] != '\0') {
str[i] = temp[i];
i++;
}
str[i] = '\0';
}
return str;
} #include <iostream>
extern "C" { char* c_function(); }
int main() {
char* str = c_function();
if (str != NULL) {
std::cout << "String from C function: " << str << std::endl;
free(str); // Must use free
}
return 0;
}Mixing the two APIs (e.g., malloc with delete) leads to undefined behavior and crashes.
5. Best Practices and Recommendations
5.1 Choice in C++ Scenarios
Because new / delete automatically handle construction and destruction, they are generally preferred in C++ projects, especially when dealing with objects that manage resources.
5.2 Use of Smart Pointers
Modern C++ (C++11 and later) provides smart pointers to automate lifetime management and avoid manual new / delete errors.
(1) std::unique_ptr – exclusive ownership.
#include <memory>
#include <fstream>
class Logger {
public:
void log(const std::string& message) { logFile << message << std::endl; }
~Logger() { logFile.close(); }
private:
std::ofstream logFile;
};
class ResourceManager {
public:
std::unique_ptr<Logger> logger;
ResourceManager() : logger(std::make_unique<Logger>()) {}
};(2) std::shared_ptr – reference‑counted shared ownership.
#include <memory>
#include <mysql/mysql.h>
class DatabaseConnection {
public:
DatabaseConnection() { mysql_init(&mysql); mysql_real_connect(&mysql, "localhost", "user", "password", "db", 0, NULL, 0); }
~DatabaseConnection() { mysql_close(&mysql); }
MYSQL* get() { return &mysql; }
private:
MYSQL mysql;
};
class Server {
public:
std::shared_ptr<DatabaseConnection> db;
Server() : db(std::make_shared<DatabaseConnection>()) {}
};(3) std::weak_ptr – non‑owning reference to break cycles.
#include <memory>
#include <iostream>
class Node;
using NodePtr = std::shared_ptr<Node>;
using WeakNodePtr = std::weak_ptr<Node>;
class Node {
public:
int data;
NodePtr next;
WeakNodePtr prev;
Node(int d) : data(d) {}
};
int main() {
NodePtr n1 = std::make_shared<Node>(1);
NodePtr n2 = std::make_shared<Node>(2);
n1->next = n2;
n2->prev = n1; // weak_ptr prevents circular reference
return 0;
}5.3 Other Memory‑Management Advice
Always pair allocation and deallocation correctly ( malloc with free, new with delete), check for allocation failures, and consider memory pools for high‑frequency small allocations. Use tools such as 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.
