The use of C++ new and delete

Tip: After the article is written, the table of contents can be automatically generated. How to generate it can refer to the help document on the right


foreword

New and delete are two very important keywords in C++, which means that memory of a specified size is allocated from "free storage (heap)" and released. Even beginners can use these usages, but this is not what I want to talk about today. What I want to talk about today is the details that are easily overlooked in use and possible mistakes


1. Introduction to new and delete

First of all, new and delete always appear in pairs, and the order cannot be wrong. It must be new first and then delete. Second, new and delete are for single objects, and new[] and delete[] are for arrays. Finally, let's start with the simplest use and slowly introduce it.

2. Easy to use

1. new and delete

This code demonstrates the use of built-in objects.

The code is as follows (example):

#include <iostream>
using namespace std;

void test_1(){
    
    
	auto* p = new int;
	delete p;
}

p points to a new piece of "free storage", and delete is responsible for reclaiming this piece of storage. This should be the simplest usage. It should be noted that p is a local variable, and p will be recycled when it goes out of scope (p is on the stack). If you do not delete p here, then this piece of storage will always be there. It cannot be recycled until your program ends and is recycled by the system.

Of course, this is not all the problems faced by p, and there are other problems to be discussed later.

2. Custom objects

new and delete can also operate on custom objects.

The code is as follows (example):

#include <iostream>

using namespace std;

struct t{
    
    
    t()= default;//没有特殊操作
    explicit t(int){
    
    }//阻止隐式转换
};

void test_2() {
    
    
    auto* p = new t;//默认构造
    auto* q = new t(1);//带参构造
    delete p;
    delete q;
}

The biggest difference between usage and built-in types is in the constructor. Constructors are divided into parameters and no parameters. Different constructors of new will call different constructors. Other than that, there is no big difference. delete does not distinguish between parameters and no parameters. parameter, the same "destructor" will be called. If you apply for free storage in the custom object, remember to recycle it in the destructor.

3.new[] and delete[]

Similar to the combination of new and delete, please see the code:

#include <iostream>

using namespace std;

void test_3(){
    
    
    auto*p = new int[10];
    delete[] p;
}

The only difference is that new[] is for arrays, and [] must be added after delete.

4. Main memory exhausted

As mentioned above, there is a bug in test_1(): this bug will not appear in normal use, and will only be triggered in special circumstances. Let me describe this error in detail: the new space is on the RAM, and may even bring some SWAP space. These spaces are limited. When the space is insufficient, a std::bad_alloc error will be thrown. For this exception, you need to It catches what would otherwise cause the program to terminate abnormally. Please see the specific reproduction code:

#include <iostream>

using namespace std;

void test_4(){
    
    
    for(;;)
        auto p = new int[8192];
}

I suggest you save important work before reproducing! I am operating on windows11, because of the bugs in this system, I almost crashed it.
Unsurprisingly, the accident happened:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc

Of course, the system also gave me another reminder:
insert image description here
in the end, the screen suddenly went black twice, and the system froze for a short time! Of course, I'm not here today to find bugs for windows 11. After testing this, the operating system didn't feel right, so I restarted it. The reason is that the main memory is exhausted, causing other programs to fail to run normally.

There is a problem here that needs to be emphasized: because it is the simulation of main memory exhaustion, and my program eats up most of the main memory, so in the end my program was detected by the operating system at the same time as a "problem", although in windows11 I didn't "kill" me on the Internet. It is actually measured that if I do this on Ubuntu, I will be directly killed by the operating system. The reason is: for the stable operation of the operating system, a part of the main memory will be reserved for the operating system. Once the main memory is exhausted, the operating system will independently decide to kill some "large" programs to ensure the "self" operation .

Therefore, there is another situation: the main memory is insufficient, and my program is not the one that takes up the most at the same time, the operating system may decide not to kill my program, but kill other programs that take up a lot. However, it does not mean that we are safe, we still face a problem, that is std::bad_alloc. How to solve this problem?

Many people may not have thought about this problem, but the fact is that you will not face this problem in most scenarios. However, there is always a contingency in everything. If you don't deal with this problem, your program will terminate early, which is definitely not the result you want.

Fortunately, the C++ standard gives us a solution, see below.

5.try&catch

That's right, it made its debut. As long as it is an exception, it is under its control. The std::bad_alloc exception here is derived from std::exception, we just need to catch it.

See example:

#include <iostream>

using namespace std;

void test_5() {
    
    
    try {
    
    
        auto *p = new int;
        //...
        delete p;
    } catch (bad_alloc &e) {
    
    
        //...
    }
}

It looks perfect, the only disadvantage is that every time new needs to try&catch, which increases the complexity. Is there a slightly easier way? please look below:

6.nothrow

The nothrow version can be selected for new and delete, which is similar to the following:
insert image description here
It means that if the main memory is exhausted, or cannot be operated normally due to other reasons, no exception will be thrown.

Sample code:

#include <iostream>

using namespace std;

void test_6() {
    
    
    auto *p = new(nothrow) int;
    if(p){
    
    
		//...
	}
    delete p;
}

Here, as long as new is added with the nothrow parameter to specify the name, no exception will be thrown, but it does not mean that the application must be successful, so an if judgment is also required. The writing method is not as bloated as try&catch, and this method is recommended.

In principle, we can't know which new will cause this kind of problem, so if you don't want to have surprises, or if you really need this, just do some processing.

7. Look at the source code

The source code of the C++ standard library overloads new&delete and new[]&delete[] respectively.

// Macro for noexcept, to support in mixed 03/0x mode.
#ifndef _GLIBCXX_NOEXCEPT
# if __cplusplus >= 201103L
#  define _GLIBCXX_NOEXCEPT noexcept
#  define _GLIBCXX_NOEXCEPT_IF(...) noexcept(__VA_ARGS__)
#  define _GLIBCXX_USE_NOEXCEPT noexcept
#  define _GLIBCXX_THROW(_EXC)
# else
#  define _GLIBCXX_NOEXCEPT
#  define _GLIBCXX_NOEXCEPT_IF(...)
#  define _GLIBCXX_USE_NOEXCEPT throw()
#  define _GLIBCXX_THROW(_EXC) throw(_EXC)
# endif
#endif

//@{
    
    
/** These are replaceable signatures:
 *  - normal single new and delete (no arguments, throw @c bad_alloc on error)
 *  - normal array new and delete (same)
 *  - @c nothrow single new and delete (take a @c nothrow argument, return
 *    @c NULL on error)
 *  - @c nothrow array new and delete (same)
 *
 *  Placement new and delete signatures (take a memory address argument,
 *  does nothing) may not be replaced by a user's program.
*/
_GLIBCXX_NODISCARD void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
_GLIBCXX_NODISCARD void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
void operator delete(void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
#if __cpp_sized_deallocation
void operator delete(void*, std::size_t) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*, std::size_t) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
#endif
_GLIBCXX_NODISCARD void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__, __alloc_size__ (1), __malloc__));
_GLIBCXX_NODISCARD void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__, __alloc_size__ (1), __malloc__));
void operator delete(void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));

It can be seen from the source code: the default delete and delete[] are both noexcept, and the default new can be thrown. When we specify nothrow, it will call the noexcept version. 这个特性是C++11(201103L)以后的版本支持的,切记!For the version code name of C++, please refer to the official document, so I won't go into details here.

这里有个小插曲:函数声明为noexcept的特性由C++标准提供强保证,简而言之就是C++标准保证声明为new(nothrow)的函数一定不会抛出异常,可以放心大胆地使用。有意思的是,我们自己也可以把一个函数声明为noexcept的,编译器会对其进行优化,当然你也要保证这个函数一定不会抛出异常,假如抛出了会怎么办?你可以自己试一试。


Summary
1. There is no difficulty in general. If you pay more attention, you will not make mistakes.
2. If you have any questions, or if there is something wrong, please leave a message here, and I can receive a reminder email in my mailbox.
3. Civilized communication, please do not abuse.

Guess you like

Origin blog.csdn.net/jiexijihe945/article/details/131065953