Fundamentals 32 min read

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.

Deepin Linux
Deepin Linux
Deepin Linux
Why new/delete Beats malloc/free: A Deep Dive into C/C++ Memory Management

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 behavior

2. 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

new

first 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

malloc

requires 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

malloc

does 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));
}
free

merely 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 freed

3.3 Error Handling Mechanism

malloc

returns NULL on failure, requiring explicit checks:

int* ptr = (int*)malloc(1000 * sizeof(int));
if (ptr == NULL) {
    std::cerr << "Allocation failed" << std::endl;
}
new

throws 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

free

does 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 leak

Using new and delete ensures the destructor runs:

Person* p2 = new Person("Bob", 30);
delete p2; // Destructor runs, then memory freed

4. 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Cmallocallocationsmart pointersnew
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.