Effective C++ Item 50: Customize new and delete-understand the reasonable replacement time of new and delete

1. Reasons for overloading operator new and operator delete

  • We can overload the operator new and operator delete of the compiler, three reasons are given below

①Used to detect errors in operation

  • Here are some common errors:
    • new allocates memory, but it is not deleted, resulting in a memory leak
    • To delete the memory allocated by new multiple times will lead to ambiguous behavior
    • All of the above errors can be detected
  • There is also an error condition:
    • Some program manipulation data may cause data "overruns" (the write point is after the end of the allocated memory) or "underruns" (the write point is before the beginning of the allocated block)
    • If we write an operator new ourselves, we can over-allocate memory and place specific bytepatterns (namely signatures) with extra space (before or after the client's block). The operator delete can check whether the above signature is intact . If it is not, it means that overruns or underruns occurred at a certain point of declaration in the allocation area. At this time, operator delete can mark this fact and the pointer that caused trouble
    • The delete written by ourselves can be tested, so we won’t worry about overwriting errors.

②In order to strengthen efficiency

  • The operator new and operator delete that come with the compiler are mainly used for general purposes:
    • They can be accepted not only by long-running programs (such as web servers), but also by programs that take less than one second to execute
    • They must deal with a series of requirements , including large blocks of memory, small blocks of memory, mixed size memory
    • They must accept a variety of allocation patterns, ranging from the dynamic allocation of a large number of blocks during the life of the program to the continuous allocation and return of a large number of short-lived objects
    • They must consider the problem of fragmentation, which will eventually cause the program to fail to meet the memory requirements of large blocks, even if there is enough free memory that is scattered into many small blocks
  • In response to the above situations, therefore:
    • Sometimes the compiler's built-in operator new and operator delete may not be friendly to your program
    • If you have a deep understanding of your own program, then you can overload operator new and operator delete yourself to customize the version suitable for your own program

③To collect statistics on usage

  • Before customizing new and delete, you should first collect how your software uses its dynamic memory:
    • What is the size distribution of allocated blocks?
    • What is the life distribution?
    • They tend to be allocated and returned in FIFP order or LIFO order or random order?
    • Do their usage patterns change over time, which means that your software has different allocation/return patterns at different stages?
    • What is the maximum amount of dynamic allocation used at any time?
  • Customized versions of new and delete allow us to collect the above information

2. Some other considerations about overloading new and delete

  • Below we have overloaded operator new , which has some minor errors, which will be improved later. code show as below:
static const int signature = 0XDEADBEEF;

typedef unsigned char Byte;


//这段代码有若干错误

void* operator new(std::size_t size) throw(std::bad_alloc)

{

    using namespace std;

    size_t realSize = size + 2 * sizeof(int); //我们实际申请两倍signature的大小


    void* pMem = malloc(realSize); 申请内存

    if (!pMem)

        throw bad_alloc();


    //将signature写入内存的最前部

    *(static_cast<int*>(pMem)) = signature;

    //将signature写入内存的最后

    *(reinterpret_cast<int*>

    (static_cast<Byte*>(pMem) + realSize - sizeof(int))

    ) = signature;


    //返回指针,指向恰位于第一个signature之后的内存位置

    return static_cast<Byte*>(pMem) + sizeof(int);

}
  • Disadvantages of this code: Violation of the C++ memory application standard: For example, Item 51 says that there should be a loop in operator new to repeatedly call a new-handling function, but this category does not (see Item 51 for details) 

Failure to follow the "alignment" operation

  • Overview of aligning operations:
    • Many computers require specific types to be placed at specific memory addresses. For example, it may require that the address of the pointer must be a multiple of 4, and the address of a double must be a multiple of 8.
    • If there is no such constraint, it may cause hardware abnormality during runtime. Some systems may not be like this. Some compilers will also provide better efficiency under aligning conditions. For example, the double on the Intel x86 architecture can be aligned to any byte boundary, but if it is 8byte aligned, its access speed will be faster
    • Operator new is no exception. C++ requires all pointers returned by operator new to have proper alignment (depending on the data type)
  • The new we overloaded above does not follow the alignment operation:
    • In the above function, we return a pointer applied from malloc and offset by an int size
    • If we assign the address returned by new to double, then we will run on a machine where "int is 4bytes and double must be 8bytes aligned", the program may crash or reduce efficiency
  • Used to test the pros and cons of the manager:
    • With the alignment operation, we can distinguish professional quality managers among many memory managers, because a good manager will operate according to alignment
    • But sometimes it is not necessary:
      • ① Some compilers have switched to debug state and mark state in their memory manager functions. There are commercial products on many platforms that can replace the memory manager that comes with the compiler. If you need them to improve the performance and performance of your program, the only thing you have to do is to relink. Of course, you need to pay
      • ②We can choose the memory manager in the development source code. For example, Pool in Boost is such an allocator, which is very helpful for the most common "allocation of a large number of small objects". Ordinary memory managers usually lack portability, alignment considerations, thread safety, etc. But Boost has all these conditions. So if you want to write your own memory manager, you can refer to these development source codes

Three, a summary of overloading new and delete

  • The following is a summary of the reasons for overloading new and delete:
    • To detect application errors (as shown above)
    • In order to collect usage statistics of dynamically allocated memory (as shown above)
    • In order to increase the speed of distribution and return:
      • The new provided by the system is often (although not always) slower than the new defined by itself, especially when the custom new is designed for a specific type of object
      • The class-specific allocator is an instance of a "fixed block size" allocator. For example, the Pool library provided by Boost is
      • If your program is a single-threaded program, but the memory manager of your compiler is thread-safe, you may be able to write a layout thread-safe allocator to greatly improve the speed
      • Of course, before getting the value of new and delete to speed up the speed, first analyze the program to confirm whether the program bottleneck occurs in these functions.
    • In order to reduce the space overhead caused by the default memory manager:
      • The new provided by the system is often (although not always) slower than the new defined by itself. They often use more memory because they often bring some additional overhead on each allocation block.
      • The allocator developed for small objects (such as the Pool library provided by Boost) essentially eliminates such additional overhead
    • To compensate for the non-optimal alignment in the default allocator:
      • As mentioned earlier, on the x86 system, the principle of the fastest access speed for doubles is that they are aligned with 8 bytes. But the compiler's built-in operator new does not guarantee that the dynamically allocated double will be aligned to 8 bytes. In this case, replacing the default operator new with an 8-bytes all-in-one version can greatly improve the program
    • In order to cluster related objects:
      • If you know the certain data structures are often used together, and you want that data to "memory page fault," the frequency to a minimum, you can create another heap for this data structure , So they can be clustered on as few memory pages as possible
      • The "placement version (see Item 52)" of new and delete may accomplish such a clustering behavior
    • In order to obtain non-traditional behavior:
      • Sometimes you want to delete and operator new compilers do not incidental things: for example, want to block in the shared memory allocation and restitution, but the only way to manage the memory of only C API functions, you can write A customized version of new and delete (probably the placement version, see Item 52), you can put a C++ jacket on the C API, or you can write a custom delete, in which all returned memory contents will be overwritten 0, increase safety

Four, summary

  • There are many reasons to write your own new and delete, including improving performance, debugging heap usage errors, and collecting heap usage information

Guess you like

Origin blog.csdn.net/www_dong/article/details/113849448