C/C++ memory management: analysis allocation, release and optimization

Table of contents

introduction

1. Stack and heap memory

1.1 Stack memory

1.2 Heap memory

1.3 Examples

C example

C++ example 

Two, C language memory management method

2.1 malloc function

introduce

usage example

Principle Analysis

2.2 calloc function

introduce

usage example

Principle Analysis

2.3 realloc function

introduce

explain

effect

usage example

Principle Analysis

2.4 free function

introduce

usage example

Principle Analysis

2.5 Hanging Pointer

concept

3. C++ memory management method

3.1 new operator

example

3.2 delete operator

example

3.3 Precautions

3.4 new/delete operation built-in type

1. Use new to allocate memory:

3.5 new and delete operation custom type

Fourth, the realization principle of new and delete

4.1. operator new 和 operator delete:

Example usage

 4.2 operator new[] 和 operator delete[]:

Example usage

4.3 Supplements

1. New class type processing:

code example

 2. The class type processing of delete:

code example


introduction

In the field of computer programming, memory management is a critical task, especially in low-level programming languages ​​such as C and C++. Good memory management practices can ensure program performance and stability. This article will explore various aspects of C/C++ memory management in depth, and help readers better understand how to correctly allocate and release memory, and improve programs through optimization strategies.

1. Stack and heap memory

1.1 Stack memory

Stack memory is an area stored in computer memory that manages local variables and function calls . Stack memory works as follows:

  • Automatic management : The allocation and release of stack memory is automatically handled by the compiler. When entering a function, the compiler allocates memory for its local variables on the stack, and when the function exits, the memory occupied by these local variables is automatically released. This automatic management mechanism ensures that the lifetime of local variables matches the lifetime of function calls.

  • Fast speed : Since the management of stack memory is automatically done by the compiler, memory allocation and release operations on the stack are very fast. This makes stack memory suitable for temporary, short-lived variables.

  • Limited size : The size of the stack is usually fixed, depending on the operating system and compiler settings. This means that the capacity of stack memory is limited, and too many local variables may lead to stack overflow.

  • Last-in-first-out ( LIFO ) : Stack memory follows the principle of last-in-first-out. That is, the last local variable allocated is the first to be freed.

Stack memory is usually used to store function parameters, local variables, and function call information. It plays an important role in handling function calls and returns

1.2 Heap memory

Heap memory is a memory area dynamically allocated when the program is running to store data structures and objects. Features of heap memory include:

  • Manual management : Unlike the stack, the allocation and release of heap memory needs to be done explicitly by the programmer. In C, mallocheap memory is allocated using functions, while in C++, this can be newachieved using operators. To release the heap memory, you need to use the corresponding function ( freeand delete) to manually release it to avoid memory leaks .

  • Flexible size : There is no fixed limit on the size of the heap memory, allowing dynamic allocation of memory at runtime. This makes heap memory suitable for storing data structures of indeterminate size , such as dynamic arrays and complex objects.

  • Slow speed : Since the allocation and release of heap memory requires the programmer to explicitly operate, the operation speed is relatively slow. Frequent heap memory allocations and deallocations can affect program performance .

  • Beware of memory leaks : Since heap memory needs to be freed manually, memory leaks can occur if a block of memory is not freed properly when it is no longer needed. A memory leak can cause a program to consume more and more memory, which can eventually lead to a crash.

Heap memory is typically used to store data that has a long life cycle, is indeterminate in size, or needs to be allocated dynamically.

1.3 Examples

Let's look at some examples:

C example

C++ example 

 The difference between the two is only in the way of opening up space and releasing space, but the basic logic is similar! !

Two, C language memory management method

mallocWhen it comes to the dynamic memory management functions ( , calloc, reallocand free) in C language , it is very important to understand their usage and principle. Below are detailed descriptions of each function, usage examples, and anatomy of the inner workings.

2.1 malloc function

introduce

mallocThe function is used to allocate a memory block of the specified size and returns a pointer to the allocated memory. The initial value in the memory it allocates is undefined , usually garbage . Returns if allocation fails NULL.

usage example

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    ptr = (int *)malloc(sizeof(int));  // 分配一个 int 大小的内存块

    if (ptr != NULL) {
        *ptr = 42;
        printf("Value: %d\n", *ptr);

        free(ptr);  // 释放分配的内存
    }

    return 0;
}

Principle Analysis

mallocThe function allocates a contiguous memory area in the heap memory according to the requested size. It returns a pointer to the start address of the allocated memory region. When called malloc, the system searches for a suitable block of free memory and marks it as taken so that future allocations do not overlap.

2.2 calloc function

introduce

callocThe function is used to allocate a specified number and size of memory blocks and initialize the allocated memory to zero . It takes two arguments, the number of memory blocks required and the number of bytes in each block , and returns a pointer to the allocated memory. Returns if allocation fails NULL.

usage example

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    ptr = (int *)calloc(1, sizeof(int));  // 分配一个 int 大小的内存块,初始化为零

    if (ptr != NULL) {
        printf("Value: %d\n", *ptr);

        free(ptr);  // 释放分配的内存
    }

    return 0;
}

Principle Analysis

callocThe function allocates a contiguous area of ​​memory in heap memory whose size is the number of required memory blocks multiplied by the number of bytes in each block. After the allocation is complete, the system sets all bytes in the allocated memory region to zero .

2.3 realloc function

introduce

reallocFunction used to reallocate the size of the allocated memory. It takes a pointer to the allocated memory and the number of bytes of memory needed, and returns a pointer to the reallocated memory. Returned if reallocation fails NULL. After calling realloc, the original pointer may be freed, so the new pointer returned should be used.

explain

  • Function prototype:void *realloc(void *ptr, size_t size);
  • Parameters ptr: A pointer to a block of memory previously allocated by malloc, callocor . reallocIf ptris a null pointer (ie NULL), then reallocbehaves as if malloc.
  • Parameters size: The new size of the memory block to reallocate, in bytes.

effect

  1. If the pointer passed in ptris NULL, then reallocwill behave like malloc, allocate a new memory block, and return a pointer to this memory.
  2. sizeIf a parameter of 0 is passed in , reallocthe behavior of will vary depending on the implementation, but will usually free ptrthe memory block pointed to by and return a null pointer. This can be used to free allocated memory blocks.
  3. If the passed in ptrpointer is not NULL, and sizeis greater than 0, reallocan attempt will be made to reallocate ptrthe memory block pointed to by to make it size sizebytes. This can result in one of the following:
    • If the size of the original memory block is large enough to accommodate the new size, the memory block will be reallocated without changing its address, and the reallocoriginal pointer will be returned.
    • If the size of the original memory block is not enough to accommodate the new size, reallocit will try to find a large enough block in memory to accommodate the new size of data, and then copy the original data to the newly allocated memory block, the original memory block will be released, and reallocreturn Pointer to newly allocated memory.

It should be noted that reallocdata copying and memory block movement may occur during the process of reallocating memory blocks, so it needs to be used with caution in performance-sensitive scenarios . At the same time, reallocafter using , the original pointer may be invalid , and the returned pointer of should always be used reallocto access the reallocated memory.

usage example

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    ptr = (int *)malloc(sizeof(int));  // 分配一个 int 大小的内存块

    if (ptr != NULL) {
        *ptr = 42;

        ptr = (int *)realloc(ptr, sizeof(int) * 2);  // 重新分配为 2 个 int 大小的内存块
        if (ptr != NULL) {
            printf("Value: %d\n", *ptr);

            free(ptr);  // 释放分配的内存
        }
    }

    return 0;
}

Principle Analysis

reallocThe function reallocates the size of the allocated memory. The memory block may be shrunk if the new size is smaller than the original size. If the new size is larger than the original size, the system may reallocate a larger memory block next to the original memory block or other suitable location, and copy the data from the original memory block to the new memory block. If the reallocation fails, the original memory block will remain unchanged.

2.4 free function

introduce

freefunction is used to free memory previously allocated by malloc, callocor . reallocAfter freeing memory, pointers to that memory become invalid and should no longer be used.

usage example

#include <stdlib.h>

int main() {
    int *ptr;

    ptr = (int *)malloc(sizeof(int));  // 分配一个 int 大小的内存块

    if (ptr != NULL) {
        *ptr = 42;

        free(ptr);  // 释放分配的内存
    }

    return 0;
}

Principle Analysis

freeThe function marks a block of memory previously allocated by the dynamic memory allocation function as free so that it can be reused in future allocations. Freed memory is not immediately returned to the operating system, but is left on the heap for future mallocor calloccalls.

In summary, the dynamic memory management functions ( malloc, calloc, reallocand free) allow more flexible memory usage by allowing memory to be allocated and deallocated at runtime. But make sure to release memory in good time to avoid memory leaks and dangling pointer problems. At the same time, be aware that reallocating memory may cause data duplication, which may affect performance.

2.5 Hanging Pointer

concept

A dangling pointer is a pointer to freed or invalid memory. A pointer becomes a dangling pointer when it points to a memory block that has been freed or to an object that is no longer valid. Accessing dangling pointers may lead to program crashes, undefined behavior, or unpredictable results.

There are two main reasons for dangling pointers:

1. The pointer is not nulled after releasing the allocated memory: After using freeor deleteto release the memory, if the pointer is not set to NULL, the pointer still maintains the previous address, but the pointed memory is invalid. Such a pointer is a dangling pointer.

int *ptr = (int *)malloc(sizeof(int));
free(ptr);  // 内存释放
// 这时 ptr 是一个悬挂指针

2. A function returns a pointer to a local variable: If a function returns a pointer to its local variable and you try to use that pointer after the function returns, you will get a dangling pointer because the lifetime of the local variable ends after the function returns .

int *getLocalPtr() {
    int x = 10;
    return &x;  // 返回局部变量的指针
}

int main() {
    int *ptr = getLocalPtr();  // ptr 变成悬挂指针
    // 后续对 ptr 的使用将导致未定义行为
    return 0;
}

3. C++ memory management method

When it comes to dynamic memory management in C++ programming, "new" and "delete" are two very important keywords. They are used to allocate and free memory while the program is running in order to create and destroy objects on the heap. The following is a detailed explanation of "new" and "delete":

3.1 new operator

"new" is the operator used in C++ to dynamically allocate memory on the heap. Its basic syntax is as follows: 

where Tis the data type of the memory to be allocated and pointeris a pointer to the allocated memory. The "new" operator performs the following steps:

  • Allocates memory of sufficient size to store Tobjects of type .
  • Call Tthe type's constructor to initialize the new object.
  • Returns a pointer to the new object.

example

3.2 delete operator

"delete" is the operator used to free memory allocated by "new". Its syntax is as follows:

where pointeris a pointer allocated via "new". The "delete" operator performs the following steps:

  • Call the pointed-to object's destructor to clean up resources.
  • Frees the memory occupied by the object.
  • Invalidates a pointer to avoid accesses to freed memory.

example

3.3 Precautions

  • Each "new" call must be freed with a corresponding "delete" operation to avoid memory leaks.
  • Do not try to call "delete" multiple times on the same pointer, this may result in undefined behavior.
  • To avoid dangling pointers, set the pointer to nullptr after freeing the memory.

C++11 introduces replacements for "new" and "delete": "new[]" and "delete[]" , which are used to allocate and deallocate arrays. For example:

std::unique_ptrHowever, a better practice is to use smart pointers (eg , )  in modern C++ std::shared_ptrto reduce the complexity and risk of manual memory management. These smart pointers can automatically free memory when they go out of scope.

3.4 new/delete operation built-in type

The "new" and "delete" operators work similarly to custom types when dealing with built-in types (such as integers, floats, etc.). Here's an example of how to use the "new" and "delete" operators with built-in types:

1. Use new to allocate memory:

2. Use delete to release memory:

It should be noted that the memory allocated by the "new" operator needs to be released by the corresponding "delete" operator to avoid memory leaks. Also, if you forget to call "delete" after allocating memory, you will cause a memory leak.

However, when dealing with built-in types, it is generally preferable to use automatic storage on the stack, which means that the variable is automatically deallocated when the scope in which it resides ends. This avoids the complexity and risks of manual memory management. For example:

3.5 new and delete operation custom type

In C++, you can use the "new" and "delete" operators to dynamically allocate and free memory of user-defined types, ie user-defined class objects. Here is an example of how to use the "new" and "delete" operators when dealing with custom types:

class Person {
public:
    Person(const std::string& name, int age) : name(name), age(age) {}
    void DisplayInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
private:
    std::string name;
    int age;
};

1. Use new to allocate a custom type object:

 2. Use delete to release the custom type object:

Note the following two points:

  • The "new" operator calls the class's constructor to initialize the object.
  • The "delete" operator calls the class's destructor to clean up resources and free memory.

However, for better memory management, modern C++ recommends using smart pointers instead of explicit "new" and "delete" operations. Here's std::unique_ptran example of using to handle custom type objects:

#include <memory>

// ...

std::unique_ptr<Person> personPtr = std::make_unique<Person>("Bob", 30); 
 // 使用 unique_ptr 分配一个 Person 对象

In this case, personPtrthe associated memory is automatically freed when the goes out of scope, no need to call "delete" manually.

If multiple smart pointers share an object, you can use std::shared_ptr:

#include <memory>

// ...

std::shared_ptr<Person> sharedPersonPtr = std::make_shared<Person>("Charlie", 40);  
// 使用 shared_ptr 分配一个 Person 对象

Using smart pointers avoids many of the problems of manual memory management and provides better memory safety.

Fourth, the realization principle of new and delete

When we use dynamic memory allocation and release in C++, such as newand deleteoperators, we actually call a set of functions defined in the C++ standard library for memory allocation and release. These functions are called "globally configured allocation functions" and can be <new>accessed by including the header file.

4.1. operator newand operator delete:

This pair of functions is used for memory allocation and deallocation of a single object. Their prototypes are:

  • operator new: This function is used to allocate a memory block of the specified size on the heap and returns a pointer to the allocated memory. It takes low-level details like memory alignment into account. It can throw an exception if allocation fails std::bad_alloc.

  • operator delete: This function is used to free the memory block previously operator newallocated by . It accepts a pointer to a memory block as a parameter, and can perform some necessary cleanup after freeing the memory.

Example usage

 4.2  operator new[]and operator delete[]:

This pair of functions is used to allocate and free array memory. Their prototypes are:

  • operator new[]: This function is used to allocate an array memory of a specified size on the heap, and returns a pointer to the allocated memory. It is operator newsimilar to but for array allocation.

  • operator delete[]: This function is used to free the array memory previously operator new[]allocated by . It accepts a pointer to a memory block as a parameter, and can perform some necessary cleanup after freeing the memory.

Example usage

These functions typically call an underlying memory allocator (such as one provided by the operating system) to perform the actual allocation and deallocation. They usually guarantee some basic thread safety in a multithreaded environment.

It should be noted that although these functions work well in most cases, in some special cases, you may consider overloading these functions to implement custom memory management logic, such as implementing memory pools or Do memory usage tracking.

4.3 Supplements

In C++, the newand deleteoperator is mainly used to dynamically allocate and release the memory of class type objects. Of course, they can also be used to allocate and free memory for built-in data types (such as integers, floating-point numbers, etc.), but in most cases, built-in data types are usually allocated and freed automatically on the stack.

1. newClass type processing:

When an object of a class type is created using newthe operator, it performs the following steps:

  1. Allocates memory of sufficient size to store objects of the class type.
  2. Call the constructor of the class to initialize the new object.
  3. Returns a pointer to the new object.

code example

class MyClass {
public:
    MyClass() { std::cout << "Constructor called." << std::endl; }
    ~MyClass() { std::cout << "Destructor called." << std::endl; }
};

MyClass* objPtr = new MyClass;  // 使用 new 创建一个 MyClass 对象
delete objPtr;                  // 使用 delete 释放 MyClass 对象

 2. deleteClass type handling:

When using deletethe operator to free an object of a class type, it performs the following steps:

  1. Call the class's destructor to clean up object resources and perform necessary cleanup.
  2. Frees the memory occupied by the object.

code example

MyClass* objPtr = new MyClass;  // 使用 new 创建一个 MyClass 对象
delete objPtr;                  // 使用 delete 释放 MyClass 对象

It is important to note that these steps ensure that in the case of dynamically allocated memory, objects are properly constructed and destructed at the appropriate time. For built-in data types, there is usually no need to explicitly use newand delete, since they are usually automatically allocated and freed on the stack.

Whether using newor new[], and using deleteor delete[], the constructor and destructor are called for each object in the array to ensure that the objects are constructed and destructed at the correct time.

Guess you like

Origin blog.csdn.net/weixin_57082854/article/details/132173117