C++: Memory management (new / delete)

Article directory

  • 1. C/C++ memory distribution
  • 2. Dynamic memory management method in C language: malloc/calloc/realloc/free
  • 3. C++ memory management method
    • 1. New/delete operation built-in types
    • 2. New / delete operation custom type
  • 4. operator new and operator delete functions
  • 5. Implementation principles of new and delete
    • 1. Built-in types
    • 2. Custom type
  • 6. Positioning new expression (placement-new)
  • 7. The difference between malloc/free and new/delete

1. C/C++ memory distribution


Let’s look at a piece of code and related memory distribution issues

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
int globalVar = 1;
static int staticGlobalVar = 1;
voidTest()
{<!-- -->
static int staticVar = 1;
int localVar = 1;
int num1[10] = {<!-- --> 1, 2, 3, 4 };
char char2[] = "abcd";
char* pChar3 = "abcd";

int* ptr1 = (int*)malloc(sizeof (int)* 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)* 4);
free(ptr1);
free(ptr3);

std::cout << "sizeof(num1):" <<sizeof(num1) << std::endl;
std::cout << "sizeof(char2):"<<sizeof(char2) << std::endl;
std::cout << "strlen(char2):" << strlen(char2) << std::endl;
std::cout << "sizeof(pChar3):" << sizeof(pChar3) << std::endl;
std::cout << "strlen(pChar4):" << strlen(pChar3) << std::endl;
std::cout << "sizeof(ptr1):" << sizeof(ptr1) << std::endl;
}

int main()
{<!-- -->
Test();
return 0;
}

  • globaVar is a global variable, accessible to all files, stored in the data segment.
  • staticGlobalVar is a static global variable that can only be accessed by the current file and is stored in the data segment.
  • staticVar is a static local variable that can only be accessed by the current function and is stored in the data segment.
  • localVar is a local variable, stored on the stack.
  • num1 is a local variable, stored on the stack.

  • char2 is the name of the array. The string "abcd" is stored in the constant area. When creating the array, a copy of "abcd\0" In the stack space, the address of the first element 'a' is the address of the array name.In the stack space.
  • *char2 is a dereference to the address of the first element of the array, *char2 is equivalent to the first element of the array a, in the stack space.
  • pChar3 is a pointer variable, pointing to the constant string "abcd" stored in the data area, const is limited to *pChar3 , stored on stack.
  • *pChar3 is a dereference to pChar3, which is actually a in the data area, in the data section
  • ptr1 points to the first address of the space returned by malloc, but ptr1 itself is temporarily created by the main function , stored in stack space
  • *ptr1 is the first four-byte space data in the dynamically allocated space, stored in the heap space

2. Dynamic memory management method in C language: malloc/calloc/realloc/free

void Test()
{<!-- -->
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);

  int* p2 = (int*)calloc(4, sizeof(int));
  int* p3 = (int*)realloc(p2, sizeof(int) * 10);

  //free(p2); // Is free(p2) needed here?
  free(p3);
}

Here p2 does not need to be released manually. realloc If the space application is successful, the original space will be released automatically. font>

  • The only difference between malloc and calloc is that calloc will initialize the allocated space to 0, while malloc will not The applied space is initialized.
  • realloc: If the original address can store the newly opened space, the original address will be expanded directly; if the original address does not have such a large space, a new space will be found and the original space will be automatically released. Returns the starting address of the new space.

3. C++ memory management method

The C language memory management method can continue to be used in C++, but it is ineffective in some places and is more troublesome to use. Therefore, C++ has proposed its own memory management method: via new and delete operator for dynamic memory management.

1. New / delete operation built-in types

void Test()
{<!-- -->
  // Dynamically apply for a space of type int, which will not be initialized.
  int* ptr1 = new int;

  // Dynamically apply for a space of type int and initialize it to 10
  int* ptr2 = new int(10);

  // Dynamically apply for ten int type spaces and will not initialize them.
  int* ptr3 = new int[10];

  // Dynamically apply for ten int type spaces and initialize them according to the list
  int* ptr4 = new int[10]{<!-- -->1, 2, 3}; //The fourth element is initialized to 0 by default

  delete(ptr1);
  delete(ptr2);
  delete[](ptr3);
  delete[](ptr4);
}

Note:
To allocate and release space for individual elements, use the new and delete operators.
To allocate and release contiguous space, use the new[] and delete[] operators.
There is no difference in usage between new and malloc. The difference is that new can initialize that space at the same time when applying for space< /strong>, and malloc can only apply for space.

2. New / delete operation custom type

class A
{<!-- -->
  public:
    A(int a)
      :_a(a)
    {<!-- -->
      cout << "A(int a): " <<this << endl;
    }

    ~A()
    {<!-- -->
      cout << "~A(): " <<this << endl;
    }

  private:
    int _a;
};

int main()
{<!-- -->
  //The biggest difference between new/delete and malloc/free is that new/delete will also call the constructor and destructor to open and release space for custom types.
  A *ptr1 = (A*)malloc(sizeof(A));
  A *ptr2 = new A(1);
  free(ptr1);
  delete(ptr2);

  return 0;
}

The result is as follows:

new and delete will call the constructor and destructor of the custom type.
But malloc and free will not, they will only apply for space and release space.

new is a continuous space. If there are several elements, the constructor and destructor will be called several times. At the same time, the order of construction first and then destruction will be followed.

// new[] / delete[]
  A* ptr3 = new A[3]{<!-- -->1, 2, 3}; // C++11 uses list initialization
  delete[](ptr3);

Here is a more complex stack custom type

class Stack
{<!-- -->
  public:
    Stack(int capacity = 4)
    :_capacity(capacity)
    ,_top(0)
    {<!-- -->
      cout << "Stack(int capacity = 4)" << endl;
      _array = new int[_capacity];
    }

    ~Stack()
    {<!-- -->
      cout << "~Stack()" << endl;
      delete[](_array);
      _array = nullptr;
      _capacity = _top = 0;
    }

  private:
    int* _array;
    int _capacity;
    int _top;
};

int main()
{<!-- -->
  Stack* p1 = new Stack(5);
  delete(p1);

  return 0;
}

For custom types, the difference between new/delete and malloc/free is that new/delete will call the constructor and destructor respectively.

Here, there is definitely a problem with free(p1). free will not call the destructor of the custom type before releasing the custom type object space. Then The space pointed to by _array has not been released, causing a memory leak.

For the new operation, what should be done if new fails?
In C++, exceptions are handled by catching exceptions, which will be explained in detail later. Here we briefly show the following usage.

void Test()
{<!-- -->
  char* p1 = new char[1024*1024*1024];
  cout << (void*)p1 << endl; // The char* type cannot use cout directly and will be automatically recognized as a string.

  char* p2 = new char[1024*1024*1024];
  cout << (void*)p2 << endl;
}

int main()
{<!-- -->
  try
  {<!-- -->
    Test();
  }
  catch(const std::exception & amp; e)
  {<!-- -->
    cout << "1" << endl;
    std::cerr << e.what() << '\
';
  }

  return 0;
}

If the space creation fails for the second time, the exception will be caught directly and printed out, and the program will not crash.

4. operator new and operator delete functions

new and delete are operators for dynamic memory application and release, operator new and operator delete are global functions provided by the system.
new At the bottom level, apply for space by calling operator new global function. delete At the bottom level, call < /strong> operator delete global function to free up space.

The operator new and operator delete functions are library global functions, which are encapsulations of malloc and free and do not call the constructor. and destructor.

If the application for space fails, an exception will be thrown directly, and the original malloc can only return 0.

/*
operator new: This function actually applies for space through malloc, and returns directly when malloc successfully applies for space; apply for space
If it fails, try to implement insufficient space countermeasures. If the countermeasures are set by the user, continue to apply, otherwise an exception will be thrown.
*/
void *__CRTDECL operator new(size_t size) __THROW1(__STD bad_alloc)
{<!-- -->
  // try to allocate size bytes
  void *p;
  while ((p = malloc(size)) == 0)
    if (_callnewh(size) == 0)
    {<!-- -->
      // report no memory
      // If the memory application fails, a bad_alloc type exception will be thrown here.
      static const std::bad_alloc nomem;
      _RAISE(nomem);
    }

  return(p);
}

/*
operator delete: This function finally releases space through free
*/
void operator delete(void *pUserData)
{<!-- -->
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK); /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg(pUserData, pHead->nBlockUse);
     __FINALLY
         _munlock(_HEAP_LOCK); /* release other threads */
     __END_TRY_FINALLY
     return;
}
/*
implementation of free
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

The following three operations for dynamically allocating memory space for built-in types are the same.

int main()
{<!-- -->
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);

  int* p2 = new int;
  delete(p2);

  int* p3 = (int*)operator new(sizeof(int));
  operator delete(p3);

  return 0;
}

Then the following code can also know how the underlying layer is called.

Stact* p1 = new Stack[10];
delete[](p1);

First, operator new is called to open up a space of 160 bytes. Then the constructor is called 10 times, creating 10 custom objects of the Stack class. Define type object.

However, through debugging, I found that it was not 160 bytes that were opened, but 168 bytes. The parameter sz of operator new was passed in. is 168

After the space is allocated, the value of p1 is 0x614c28. By observing 0x614c20, it is found that 8 more bytes have been allocated. Space, which stores the number of array elements 10

Replace the code, Stack* p1 = new Stack[8];
Found that the address pointed to by p1 is indeed 8 forward by 8

This number exists because the custom type has a destructor and you need to know how many times the destructor needs to be called.

At this time, if I mistakenly use delete instead of delete[], it will directly cause the program to crash. The program actually starts applying at 0x614c20 168 bytes of space. Using delete[] will automatically pass the address into operator delete, and delete will not be processed in this way, and the original 0x614c28 will be passed into the function. This address is not the first address of the applied space, and the release fails.

But if it is a built-in type, it may not crash

int* p2 = new int[10];
delete(p2);

Built-in types do not have destructors, so there is no need to open an extra space to record how many times the destructor needs to be called. In this way, the program will not crash.

Through the above debugging, you can get the following programming notes.

new/delete, new[]/delete[], malloc/free must be used together, otherwise the result will be undetermined.

5. Implementation principles of new and delete

1. Built-in types

If you are applying for a built-in type of space, new and malloc, delete and free are basically similar, with some differences. yes:
new/delete applies for and releases the space of a single element, while new[]/delete[] applies for and releases continuous space.
new will throw an exception when the application fails, and malloc will return NULL

2. Custom type

  • The principle of new

    1. Call the operator new function to construct the space
    2. Execute the constructor on the requested space to complete the construction of the object
  • The principle of delete

    1. Execute the destructor on the space to complete the cleanup of resources in the object
    2. Call the operator delete function to release the space of the object
  • The principle of new[]

    1. Call the operator new[] function, and actually call the operator new function in operator new[] to complete the application for N object spaces.
    2. Execute the constructor N times on the allocated space
  • The principle of delete[]

    1. Execute N destructors on the released object space to complete the cleanup of resources in N objects
    2. Call operator delete[] to release space. Actually call operator delete in operator delete[] to release space.

6. Positioning new expression (placement-new)

Positioning the new expression is to call the constructor to initialize an object in the allocated original memory space

Usage format:

new(place_address)type or new(place_address)type(initializer-list)

place_address must be a pointer and initializer-list is the type's initializer list.

scenes to be used:
The positioning new expression is generally used with the memory pool in practice. Because the memory allocated by the memory pool is not initialized, if it is a custom type object, you need to use new The definition expression explicitly calls the constructor for initialization.

class A
{<!-- -->
  public:
    A(int a = 0)
      :_a(a)
    {<!-- -->
      cout << "A():" <<this << endl;
    }

    ~A()
    {<!-- -->
      cout << "~A(): " <<this << endl;
    }

  private:
    int _a;
};

int main()
{<!-- -->
  A* p1 = (A*)malloc(sizeof(A)); // At this time, the space pointed to by p1 has not been initialized. Malloc only has the function of applying for space.
  new(p1)A; // Use positioning new to construct the p1 object
  p1->~A(); // The destructor can be called explicitly
  free(p1);

  A* p2 = (A*)operator new(sizeof(A)); // operator new also only applies for space and does not initialize the space
  new(p2)A(2); // Initialize using list
  p2->~A();
  operator delete(p2);

  return 0;
}

Finally, positioning new was indeed used to complete the initialization and construction of the uninitialized space.

7. The difference between malloc/free and new/delete

What malloc/free and new/delete have in common is that they both apply for space from the heap and require the user to release it manually.
The difference is:

  1. malloc and free are functions, new and delete are operators.
  2. The space requested by malloc will not be initialized, but new can be initialized.
  3. When malloc applies for space, you need to manually calculate the space size and pass it. new only needs to be followed by the type of space. If there are multiple objects, [ ] Just specify the number of objects.
  4. The return value of malloc is void*, which must be forced when used. new is not needed because new follows is the type of space.
  5. When malloc fails to apply for space, it returns NULL, so it must be null when used. new is not required, but new code> needs to catch exceptions.
  6. When applying for a custom type object, malloc/free will only open up space and will not call the constructor and destructor, while new will call the constructor after applying for space. Initialization of the object, delete will call the destructor to complete the cleanup of resources in the space before releasing the space.

End of this chapter.

syntaxbug.com © 2021 All Rights Reserved.