How many of these C++ memory leak pits have you stepped on?

2021/09/1117:56:01 technology 1408

recommended video:

How many of these C++ memory leak pits have you stepped on? - DayDayNews

In fact, when delete pArrayObjs; is called, the entire memory of pArrayObjs is released, but only the destructor of pArrayObjs[0] is called and the memory pointed to by m_pStr is released. pArrayObjs 1~4 did not call the destructor, which caused the memory pointed to by m_pStr to not be released. Therefore, we must pay attention to the matching use of new and delete. When using new [], it is best to use delete[] to apply for the memory. Then leave a question to the reader, the above code delete m_pStr; will cause the same problem? If we always have to guarantee ourselves, the pairing of new and delete is obviously difficult to avoid mistakes. You can also use unique_ptr at this time, modify it as follows:

  void MemoryLeakFunction(){ const int iSize = 5; std::unique_ptr pArrayObjs = std::make_unique(iSize); for ( int i = 0; i DoSomething(); }}  

[Article benefits] C/C++ Linux server architect learning materials plus group 812855908 (Information includes C/C++, Linux, golang technology, kernel, Nginx, ZeroMQ, MySQL, Redis , fastdfs, MongoDB ,ZK, streaming media , CDN,

, K8S, Docker , TCP/IP, coroutine, DPDK, ffmpeg , etc.)

img1 hp1 img

_p4 delete 1 (

_p4) The previous chapter has been understood, so for this example, it is easy to understand. Because of the flexibility of C++, sometimes an object pointer is converted to void *, hiding its type. In this case, the SDK is more commonly used. In fact, what is returned is not the actual type used by the SDK, but an address without a type. Of course, sometimes we will give it a friendly name, such as XXX_HANDLE. Then continue to use the above example MemoryLeakClass, the SDK assumes that the following three interfaces are provided:

  1. InitObj creates an object and returns a PROGRAMER_HANDLE (ie void *), which shields the application from its actual type
  2. DoSomething provides a function To do some things, the input parameters are the objects applied for through InitObj
  3. After the application is used, you generally need to release the objects applied for by the SDK, providing FreeObj
  typedef void * PROGRAMER_HANDLE;PROGRAMER_HANDLE InitObj(){ MemoryLeakClass* pObj = new MemoryLeakClass(); return (PROGRAMER_HANDLE)pObj;}void DoSomething(PROGRAMER_HANDLE pHandle){ ((MemoryLeakClass*)pHandle)->DoSomething();}void FreeObj(void *pObj){ delete pObj;}  

see here,Perhaps some readers have already discovered the problem. When the above code calls FreeObj, delete sees a void *, which only releases the memory occupied by the object, but does not call the object's destructor, so the memory pointed to by m_pStr inside the object is not released , Which will lead to memory leaks. The modification is naturally relatively simple:

  void FreeObj(void *pObj){ delete ((MemoryLeakClass*)pObj);}  

So in general, it is best to have a relatively senior programmer to develop the SDK. Regardless of the design and implementation, all kinds of tearful pits have been avoided as much as possible.

4. Virtual destructor

Now let’s take a look at this very error-prone scene, a very common polymorphic scene. So when you call delete pObj; will there be a memory leak?

  class Father{public: virtual void DoSomething(){ std::cout <<> protected : char* m_pStr;};void MemoryLeakVirualDestructor(){ Father * pObj = new Child; pObj->DoSomething(); delete pObj;}  

Yes,Because Father does not set the Virtual destructor, when calling delete pObj; it will directly call Father’s destructor instead of Child’s destructor, which results in the memory pointed to by m_pStr in Child, and It is not released, which leads to a memory leak. It is not absolute. When there is such a usage scenario, it is best to set the destructor of the base class as a virtual destructor. Amend as follows:

  class Father{public: virtual void DoSomething(){ std::cout <<> 

5. Object circular reference

Look at the following example, since in order to prevent memory leaks,So the smart pointer shared_ptr is used; and this example is to create a two-way linked list . For a simple demonstration, there are only two nodes as a demonstration. After the linked list is created, the linked list is traversed.
So does this example cause a memory leak?

  struct Node{ Node(int iVal) {m_iVal = iVal;} ~Node() {std::cout <<> m_pPreNode; std ::shared_ptr m_pNextNode; int m_iVal;};void MemoryLeakLoopReference(){ std::shared_ptr pFirstNode = std::make_shared(100); std::shared_ptr pSecondNode = std:: make_shared(200); pFirstNode->m_pNextNode = pSecondNode; pSecondNode->m_pPreNode = pFirstNode; //Iterate nodes auto pNode = pFirstNode; while (pNode) {pNode->PrintNode(); pNode = pNode->m_pNextNode; }}  

Let’s take a look at the picture below first,It is a schematic diagram after the creation of the linked list. It's a bit dizzy. Why is a doubly linked list drawn so complicated? The yellow backgrounds are all smart pointers or parts of smart pointers. In fact, based on the simplicity of 's doubly linked list and the complexity of the figure below, it can be imagined that although the introduction of smart pointers improves security, it loses performance. Therefore, security and performance often need to be weighed against each other. We continue to look down, where is the memory leaked?

How many of these C++ memory leak pits have you stepped on? - DayDayNews


If the function exits, then m_pFirstNode and m_pNextNode are used as local variables on the stack. The smart pointer itself calls its own destructor, and subtracts 1 from the reference count of the referenced object (shared_ptr essentially uses a reference count, when the reference is When the count is 0, the object will be deleted). At this time, as shown in the figure below, you can see that the reference count of the smart pointer is still 1, which also causes the actual memory of the two nodes to not be released, which leads to memory leaks.

How many of these C++ memory leak pits have you stepped on? - DayDayNews

You can manually call pFirstNode->m_pNextNode.reset(); force the reference count to subtract 1 to break the circular reference before the function returns.
is the same as the previous sentence. If you control it manually, you will inevitably miss out. C++ provides weak_ptr.

  struct Node{ Node(int iVal) {m_iVal = iVal;} ~Node() {std::cout <<> m_pPreNode; std::weak_ptr m_pNextNode; int m_iVal ;};void MemoryLeakLoopRefference(){ std::shared_ptr pFirstNode = std::make_shared(100); std::shared_ptr pSecondNode = std::make_shared(200); pFirstNode- >m_pNextNode = pSecondNode; pSecondNode->m_pPreNode = pFirstNode; //Iterate nodes auto pNode = pFirstNode; while (pNode) {pNode->PrintNode(); pNode = pNode->m_pNextNode.lock(); ))  

watch Look at the linked list structure after using weak_ptr as shown in the figure below,Weak_ptr just makes a weak reference to the managed object, and it does not actually control the release of the object. The object is released when the reference count is 0, and there is no need to care about the weak_ptr's weak count. Note that shared_ptr itself will also increase the weak count by 1.
Then after the function exits, when pSecondNode calls the destructor, the reference count of the object is decremented by one, the reference count is 0, the second Node is released, and the second is released. In the process of Node, the destructor of m_pPreNode is called again, the reference count of the first Node object is reduced by 1, and the reference count of the first Node object by the pFirstNode destructor is also reduced by 1, then the first Node The reference count of the object is also 0, and the first Node object is also released.

How many of these C++ memory leak pits have you stepped on? - DayDayNews


If the above code is changed to a two-way circular linked list and the code that loops through Node is removed, will the memory of Node be released in the end? This question is left to the reader.

6. Resource leakage

If there are some composition, this chapter may be a bit off topic. This chapter is about resource leaks in a broad sense, such as handle or fd leaks. These can be regarded as a little extension of memory leaks, a little extension of writing.
Look at the following example, it forgets to call CloseHandle(hFile); after operating the file, which leads to a memory leak.

  void MemroyLeakFileHandle(){ HANDLE hFile = CreateFile(LR"(C:\test\doc.txt)", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE: == h :cerr <<> DWORD  dwBufferSize; if (ReadFile(hFile, pDataBuffer, BUFFER_SIZE, &dwBufferSize, NULL )) {std::cout <<> 

As mentioned above, you can use the RAII mechanism to encapsulate hFile so that after the function exits,Call CloseHandle(hFile); directly. C++ smart pointers provide a custom deleter function, which allows us to use this deleter function and rewrite the code as follows. However, I prefer to use an implementation similar to golang defer. Readers can refer to the relevant reading part of this article.

  void MemroyLeakFileHandle(){ HANDLE hFile = CreateFile(LR"(C:\test\doc.txt)", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); std::unique_ptr<:>> phFile( &hFile, [](HANDLE* pHandle) {if (nullptr != pHandle) {std::cout << "Close Handle" << std::endl; CloseHandle(*pHandle );} }); if (INVALID_HANDLE_VALUE == *phFile) {std::cerr << "Open File error!" << std::endl; return;} const int BUFFER_SIZE = 100; char pDataBuffer[BUFFER_SIZE]; DWORD dwBufferSize; if (ReadFile(*phFile, pDataBuffer, BUFFER_SIZE, &dwBufferSize, NULL)) {std::cout <<> 
.

technology Category Latest News