Overview, analysis, prevention and troubleshooting of C/C++ memory leaks

Overview, analysis, prevention and troubleshooting of C/C++ memory leaks

If you need to reprint, please indicate the source: http://blog.csdn.net/itas109
Technical Exchange Q: 129518033

1. Concept

In a narrow sense, a memory leak is caused by the incorrect release of dynamically allocated memory, such as not deleting after new.

In a broad sense, memory leaks that fail to reclaim unused memory, such as invalid global map caches, socket handles, file handles, etc.

For long-running server background programs, memory leaks may cause very serious consequences, such as performance degradation, program crashes, and system crashes.

2. How memory leaks occur

2.1 Frequent memory leaks

The leaking code is executed multiple times, each time creating a memory leak.

2.2 Occasional memory leaks

Occasional memory leaks are only triggered in specific scenarios and cause memory leaks.

Of course, sporadic memory leaks are also relative. It is possible that an infrequently used business becomes a commonly used business. If there is a memory leak in an infrequently used business, then the memory leak at this time is a frequent memory leak.

2.3 One-time memory leak

The leaked code is executed only once.

2.4 Implicit memory leaks

Implicit memory leaks refer to memory leaks caused by the time limit for releasing memory.

This mainly refers to other problems caused by not releasing memory in time, such as program or system crashes caused by memory fragmentation causing no memory to be allocated.

like:

  • Frequent new/delete
  • Do not reclaim memory immediately after free/delete execution
  • vector.clear() in STL does not release space
  • The global cache does not set an invalidation mechanism, causing the cache to grow larger and larger

3. Classification of memory leaks

3.1 Not released

Not deleted after using raw pointer new

Example of unreleased code (delete should be called):

int main()
{
	char *str = new char[256];
	return 0;
}

3.2 Unmatched

Apply and release the correct match:

  • malloc/free : only apply/release space
  • new/delete : apply for space, call the constructor/call the destructor, release the space
  • new[]/delete[] : apply for space, call multiple constructors/call multiple destructors, and release space

The relationship between new/delete and malloc/free:

// new
void *ptr = malloc(sizeof(T)*1); // malloc分配空间
T* t = new(ptr)T; // 已分配存储中构造(placement new)

// delete
t->~T(); // 析构
free(ptr); // free释放空间

Unmatched code example (delete[] should be called):

#include <stdio.h>

class Base
{
 public:
    Base() {printf("Base()\n");}
    ~Base() {printf("~Base()\n");}
};

int main()
{  
  Base *b = new Base[2];
  delete b;
  return 0;
}

operation result

Base()
Base()
~Base()

3.3 Virtual Destruction

When the parent class destructor is not a virtual function, when the parent class pointer releases the subclass object, the destructor of the subclass will not be called, resulting in a memory leak.

The following example will generate a memory leak, change ~Base() to virtual ~Base() and it will be released normally.

#include <stdio.h>

class Base
{
public:
    Base()
    {
        str = new char[256];
        printf("Base()\n");
    }
    ~Base()
    {
        delete[] str;
        printf("~Base()\n");
    }

private:
    char *str;
};

class Derived : public Base
{
public:
    Derived() { printf("Derived()\n"); }
    ~Derived() { printf("~Derived()\n"); }
};

int main()
{
    Base *base = new Derived;
    delete base;
    return 0;
}

operation result

Base()
Derived()
~Base()

3.4 Circular references

In order to avoid memory leaks, smart pointers have been introduced since C++11. The common ones are shared_ptr, weak_ptr, and unique_ptr, among which weak_ptr is to solve the circular reference problem.

Code example for circular references:

#include <stdio.h>
#include <memory>

class B;

class A
{
public:
	A() {}
	~A() { printf("~A()\n"); }
	std::shared_ptr<B> m_B;
};

class B
{
public:
	B() {}
	~B() { printf("~B()\n"); }
	std::shared_ptr<A> m_A;
};

int main()
{
	auto a = std::make_shared<A>();
	auto b = std::make_shared<B>();

	a->m_B = b;
	b->m_A = a;

	printf("A.use_count: %ld\n", a.use_count());
	printf("B.use_count: %ld\n", b.use_count());
	return 0;
}

Running result (note that the reference count is not 0 and the destructor is not called)

A.use_count: 2
B.use_count: 2

Modify the std::shared_ptr m_A in A to std::weak_ptr m_A to solve the circular reference problem.

A.use_count: 1
B.use_count: 2
~A()
~B()

4. Prevention of memory leaks

  • Do not use heap memory, use stack memory
  • Instead of raw pointers, use smart pointers
  • Use the RAII mechanism

5. Troubleshooting ideas for memory leaks

  • Code detection (static code detection tools, dynamic memory detection tools)
  • Code Review (unreleased, unmatched, virtual destructor, circular reference)
  • Print the log backtracking business and output the memory information
  • Minimize scene recurrence

License

License under CC BY-NC-ND 4.0: Attribution-Noncommercial Use-No Derivatives

If you need to reprint, please indicate the source: http://blog.csdn.net/itas109
Technical exchange: 129518033


Reference:

  1. https://baike.baidu.com/

Guess you like

Origin blog.csdn.net/itas109/article/details/130303371