Smart pointer and memory reclaim

Source: Internet
Author: User

Java and C # both have the garbage collection function. After a piece of memory is allocated, the programmer can ignore it, but the garbage collection function automatically recycles it, freeing the programmer from complicated memory management. This is a major advantage of Java and C. After the C ++ programmer allocates a memory segment with new, it must release it with Delete, otherwise it will cause resource leakage. Therefore, some C ++ books often warn programmers: to develop good habits, new and delete should appear in pairs, always remember to release the memory back to the system. But is it that simple?

We often encounter the following situations when using C ++:

Class

{

Public:

A ();

~ A ();

Setnextptr (A * PTR)

{Pnext = PTR ;}

PRIVATE:

A * pnext;

}

Generally, in order not to cause memory leakage, we release pnext In the destructor, as shown below:

A ::~ A ()

{

If (pnext)

Delete pnext;

Pnext = NULL;

}

In general, this is enough, but in some cases, there will also be problems, as shown below:

A * ptrb = new ;;

A * ptra = new;

Ptrb-> setnextptr (ptra );

Ptra-> setnextptr (ptrb );

Delete ptrb;

This will cause a problem, because these pointers are connected into a loop, no matter from which point the deletion will cause a pointer to be deleted more than twice, which will cause the program to throw an exception. Of course, there are also some ways to solve this problem, but it is not difficult for C ++ programmers to develop a good habit, the difficulty is that sometimes this will bring you into a logical chaos, add unnecessary troubles, and sometimes even feel overwhelmed.

But how can this problem be solved? If C ++ also has the garbage collection function, this problem will certainly be solved. However, C ++ is a compilation language and does not have this function. For a long time, I have been thinking about this problem and want to find a way to free myself from such troubles. Until recently I began to learn generic programming. After seeing the introduction of smart pointers, I finally found a solution to this problem.

As you know, smart pointers have some smart features. For example, they can be automatically initialized during construction and automatically released during structure analysis. We use these features to achieve garbage collection.

First, we need to find a way to add reference records for each memory segment allocated with new, that is, to record the number of smart pointers currently pointing to it, when the last pointer to it is released, we can release the memory. As a result, we performed global overloading of new and delete and introduced the cptrmanager class.

 

Void operator Delete (void * P)

{

 

Int mark = theptrmanager. getmarkfromptr (P );

If (Mark> 0)

Theptrmanager. userdelete (Mark );

Free (P );

}

Void * operator new (size_t size)

{

 

Return theptrmanager. mallocptr (size );

 

}

Class cptrmanager

{

Public:

Int getcount (INT mark, void * P); // obtain the current reference count.

Static cptrmanager * getptrmanager (); // obtain the globally unique cptrmanager pointer.

Void userdelete (INT mark); // Delete the pointer of the mark and reset the pointer and flag.

Void * mallocptr (size_t size); // new () calls it to allocate memory;

Bool addcount (INT mark, void * PTR); // Add reference records

Bool release (INT mark, void * PTR); // reduce the reference count

Int getmarkfromptr (void * PTR); // obtain the flag through the pointer

Cptrmanager ();

Virtual ~ Cptrmanager ();

PRIVATE:

Static cptrmanager * p_this; // point to the globally unique cptrmanager pointer

Void addptr (void * PTR); // Add a newly allocated memory.

Cptrarray m_ptr; // variable array for storing the allocated pointer

Cuintarray m_count; // The reference count for storing pointers

Void * pcurrent; // pointer recently allocated

Unsigned int m_mark; // the flag of the pointer just recently allocated

Cuintarray m_removed; // The location left blank after the pointer in m_ptr is deleted

};

As the name suggests, cptrmanager is used to manage pointers. For every pointer allocated with new, it is stored in m_ptr [Index] and its reference records are stored in m_count [Index. At the same time, we add a flag for each pointer (Mark> 0, <= 0 is invalid). This flag also exists in the smart pointer (which will be seen later ), this is for double insurance, and here, this flag is equivalent to the index of the pointer in m_ptr, which also provides convenience for quick search.

The general idea is as follows: when we use new to allocate a pointer, this pointer will be stored in cptrmanager. When a smart pointer begins to possess this pointer, cptrmanager will be responsible for adding 1 to the reference count of this pointer, and vice versa. That is, when a smart pointer begins to release the ownership of this pointer, cptrmanager will be responsible for reducing the reference count of this pointer by 1, if the reference count is 0, the smart pointer is responsible for deleting the pointer.

The following is an introduction to smart pointers:

Template <class T>

Class auto_ptr

{

Public:

Auto_ptr ()

{Mark = 0; pointee = 0 ;}

Auto_ptr (auto_ptr <t> & RHs );

Auto_ptr (T * PTR );

~ Auto_ptr () {remove ();}

T * operator-> () const;

Operator T *();

T & operator * () const;

Auto_ptr <t> & operator = (auto_ptr <t> & RHs );

Auto_ptr <t> & operator = (T * PTR );

PRIVATE:

Void remove (); // release the ownership of the pointer

T * pointee; // pointer

Int mark; // The identifier of the pointer.

};

Template <class T> void auto_ptr <t>: Remove ()

{

Cptrmanager * pmana = cptrmanager: getptrmanager ();

If (pointee & pmana)

{

If (pmana-> release (mark, pointee) // reduce the reference count

{

If (pmana-> getcount (mark, pointee) = 0)

Delete pointee; // if the reference count is 0, the delete pointer

}

Else // the pointer is not in cptrmanager and may be in the stack.

{

// User decide to do

}

 

 

}

Pointee = NULL; // Reset

Mark = 0;

}

Template <class T> auto_ptr <t>: auto_ptr (auto_ptr <t> & RHs)

{

Pointee = RHS. pointee;

Mark = RHS. mark;

Cptrmanager * pmana = cptrmanager: getptrmanager ();

If (pmana)

Pmana-> addcount (mark, pointee); // Add reference records

}

Template <class T> auto_ptr <t>: auto_ptr (T * PTR)

{

Mark = 0;

Pointee = PTR;

Cptrmanager * pmana = cptrmanager: getptrmanager ();

If (pmana)

{

Mark = pmana-> getmarkfromptr (PTR); // obtain the pointer flag

If (Mark> 0)

Pmana-> addcount (mark, pointee); // Add reference records if the flag is not 0

}

}

Template <class T> auto_ptr <t> & auto_ptr <t>: Operator = (auto_ptr <t> & RHs)

{

If (pointee! = RHS. pointee)

{

Remove (); // release the ownership of the current pointer

Pointee = RHS. pointee;

Mark = RHS. mark;

Cptrmanager * pmana = cptrmanager: getptrmanager ();

If (pmana)

Pmana-> addcount (mark, pointee );

 

}

Return * this;

 

}

Template <class T> auto_ptr <t> & auto_ptr <t>: Operator = (T * PTR)

{

If (pointee! = PTR)

{

Remove ();

Pointee = PTR;

Cptrmanager * pmana = cptrmanager: getptrmanager ();

If (pmana)

{

Mark = pmana-> getmarkfromptr (PTR );

If (Mark> 0)

Pmana-> addcount (mark, pointee );

}

}

 

}

When I got there, I thought it was a success. I couldn't help but give it a try. The results show that the effect is indeed good in general cases, achieving the effect of garbage collection. The following applications:

Auto_ptr <Test> P1 = new test;

Auto_ptr <Test> P2 = p1;

Auto_ptr <Test> P3 = new test;

However, soon I found the problem during the loop test I mentioned earlier. I tested it like this:

Class Test

{

Auto_ptr <Test> P;

};

 

Auto_ptr <Test> P1 = new test;

Auto_ptr <Test> P2 = new test;

P1-> P = P2;

P2-> P = p1;

When the execution of the program leaves the scope, the two memories are not released as I imagined, but are kept in the heap until the program ends. I carefully analyzed the cause of this phenomenon and found a very interesting problem. I call it interlock.

The pointer owned by P1 is owned by two smart pointers. In addition to P1, there are also the smart pointer P in the test class owned by P2, and so on. That is to say, the reference records of the two memory pointers are both 2. When the execution of the program leaves the scope, P1 and P2 are destructed so that their reference records are all 1. Since then, no smart pointers are destructed and their reference records are changed to 0, therefore, they will be kept in the heap for a long time. This is like two locked boxes, each of which contains the other's keys, but cannot open each other. This is the mutual lock phenomenon.

But how can this problem be solved? It seems that it must be improved. At the same time, I also found that the above method does not support multithreading. Therefore, our improved method should not only solve the mutual lock phenomenon, but also support multithreading. The following is my improved method:

The first is how to discover this mutual lock phenomenon. We know that the root cause of the mutual lock phenomenon is that the smart pointer with heap memory also exists in the allocated heap memory, how to find out whether the smart pointer exists in the heap or the stack becomes the key to the problem. As a result, I introduced a new class cptr to manage pointers allocated with new, and cptrmanager is dedicated to managing cptr. As follows:

Class cptr

{

Friend class cmark;

Public:

Int getptrsize (); // get the memory size of the pointer allocated with new

Cmutex * getcmutex (); // used for Thread Synchronization

Void * getptr (); // get the pointer allocated with new

Cptr ();

Virtual ~ Cptr ();

Int getcount (); // get the reference count

Void addautoptr (void * autoptr, int automark); // Add a smart pointer with this pointer

Bool deleteautoptr (void * autoptr); // release the ownership of a smart pointer

Void setptr (void * theptr, int size, int mark, int COUNT = 0); // set a new pointer

Void operator Delete (void * P)

{

Free (P );

}

Void * operator new (size_t size)

{

Return malloc (size );

}

 

PRIVATE:

Int m_mark; // pointer flag

Int m_count; // reference count

Void * m_ptr; // assigned pointer

Int m_size; // pointer to memory size

Cptrarray autoptrarray; // a pointer array that stores all smart pointers with this pointer

Cuintarray m_automark; // flat pointer: 0 in the stack;> 0 = mark

Cmutex mutex; // used for Thread Synchronization

};

Class cptrmanager

{

Public:

Int getautomark (void * PTR); // uses the pointer of the smart pointer to obtain the smart pointer flag.

Cptrmanager ();

Virtual ~ Cptrmanager ();

Int getmarkfromptr (void * PTR );

Void * mallocptr (size_t size );

Bool bcanwrite ();

Void deleteptr (INT mark, void * PTR );

Void addptr (void * PTR, int ptrsize );

Static cptrmanager * getptrmanager ();

Cptr * getcptr (void * PTR, int mark); // obtain the cptr that stores the pointer through the pointer and pointer flag.

 

PRIVATE:

Cptrarray m_ptr; // the pointer array for storing cptr

Void * pcurrent;

Unsigned int m_mark;

Cuintarray m_removed;

Bool bwrite; // in the process of solving the mutual lock phenomenon, decline the processing of other threads

Static cptrmanager * p_this;

Cmutex mutex; // used for Thread Synchronization

Cmarktable mymarktable;

Void removelockres (); // process mutual lock memory

Void copyallmark (); // copy all cptr before processing the mutual lock

Static uint mythreadproc (lpvoid lparm); // The thread function for processing the mutual lock.

Cwinthread * mythread;

Void beginthread ()

{Mythread = afxbeginthread (mythreadproc, this, thread_priority_above_normal );}

};

The above application adds a flat pointer. In fact, this flat is equivalent to the memory pointer of the flat pointer. For example, we allocated a test pointer with new. If the mark of this pointer is 1, The auto_ptr <Test> P Flag automark in this test is 1. If a smart pointer exists in the stack, its automark is 0. And vice versa. If the automark of a smart pointer is equal to the mark of a pointer, the smart pointer must exist in the memory indicated by the pointer. But how can we get this sign? See the implementation of this function below:

Int cptrmanager: getautomark (void * PTR)

{

Csinglelock singlelock (& mutex );

Singlelock. Lock (); // Thread Synchronization

 

Int size = m_ptr.getsize ();

For (INT I = 1; I <size; I ++)

{

Cptr * thecptr = (cptr *) m_ptr [I];

If (thecptr)

{

Int ptrfirst = (INT) thecptr-> getptr (); // get the first pointer to the memory.

Int ptrend = ptrfirst + thecptr-> getptrsize (); // obtain the end pointer of the memory.

Int P = (INT) PTR; // pointer to a smart pointer

If (P> = ptrfirst & P <= ptrend) // determines whether the pointer of a smart pointer is between the beginning and the end.

Return I;

}

}

Return 0;

}

The principle of this function is: if a smart pointer exists in a memory, the pointer of the smart pointer will be between the first and last pointers of the memory.

To solve the problem of the location of the smart pointer, the next step is to find all the pointers in the memory that are locked by each other. This is a good implementation. As long as the automark of all the smart pointers with this pointer is greater than 0, the memory may be locked (note that it is only possible). Next, let's look at the implementation below:

Class cmark

{

Friend class cmarktable;

Public:

Cmark (){}

Virtual ~ Cmark (){}

 

 

Void operator Delete (void * P)

{

Free (P );

}

Void * operator new (size_t size)

{

Return malloc (size );

}

Void copyfromcptr (cptr * thecptr); // copy related information from cptr

Bool bisnoneinstack (); // determines whether all the smart pointers with this pointer are not in the stack.

Void release (); // unlock the mutual lock of the pointer

PRIVATE:

Int m_mark; // pointer flag

Cptrarray autoptrarray; // pointer array of all smart pointers with this pointer

Cuintarray automarkarray; // The identifier of all smart pointers with the pointer

 

};

Class cmarktable

{

Public:

Cmarktable () {Init ();}

Virtual ~ Cmarktable (){}

Void addcmark (cmark * thecmark );

Bool findmark (INT mark );

Void Init ();

Void dolockmark (); // process the lock issue

PRIVATE:

Cptrarray cmarkarray; // A cmark pointer array for storing pointer information copied from cptrmanager

Cptrarray clockmarkarray; // memory for storing mutual locks

Void getlockmark (); // obtain the cmark of all memory that may be locked, and the result is stored in clockmarkarray.

Bool findlockmark (INT mark); // determines whether a smart pointer exists in the pointer contained by clockmarkarray.

Void removeunlockmark (); // remove the false mutual lock memory

Void removegroup (INT automark); // groups memory with mutual deadlocks.

 

};

Here, two classes are introduced: cmark and cmarktable to quickly copy cptr in cptrmanager before processing the interlock problem, so as to prevent the normal operation of other threads from being affected. In fact, the cmark here is no different from cptr, it simply copies information from cptr, that is, it is equivalent to cptr.

In order to deal with the problem of mutual lock, we should first find out the memory pointer that may be locked by mutual locks. Let's look at the implementation of the following function:

Void cmarktable: getlockmark ()

{

Clockmarkarray. setsize (0 );

Int size = cmarkarray. getsize ();

For (INT I = 0; I <size; I ++)

{

Cmark * themark = (cmark *) cmarkarray [I];

If (themark)

{

If (themark-> bisnoneinstack ())

Clockmarkarray. setatgrow (I, themark );

}

}

}

After finding out these memories, we need to find out the memory of these locks. What is a false lock? See the following example:

For pointer ptra, if its smart pointer autoa exists in Pointer ptrb, and ptrb's smart pointer autob exists in ptra, ptra and ptrb are real locks, however, if ptrb's smart pointer autob exists in the pointer ptrc, and ptrc's smart pointer AutoC exists in the stack, ptra and ptrb are pseudo locks. How can I find out the false lock memory? See the implementation of the following functions:

Void cmarktable: removeunlockmark ()

{

Cuintarray unlockmarkarray;

Bool bnoneremoveed;

Do

{

Bnoneremoveed = true;

Unlockmarkarray. setsize (0 );

Int size = clockmarkarray. getsize ();

For (INT I = 0; I <size; I ++)

{

Cmark * themark = (cmark *) clockmarkarray [I];

If (themark)

{

Int size1 = (themark-> automarkarray). getsize ();

For (Int J = 0; j <size1; j ++)

{

Int mark = (themark-> automarkarray) [J];

If (! Findlockmark (Mark) // determines whether the smart pointer exists in the pointer contained by clockmarkarray.

{

Unlockmarkarray. insertat (0, I); // record to remove

Bnoneremoveed = false;

Break;

}

}

}

Else

{Unlockmarkarray. insertat (0, I );

Bnoneremoveed = false;

}

}

Int size2 = unlockmarkarray. getsize ();

For (int K = 0; k <size2; k ++)

{

Int M = unlockmarkarray [k];

Clockmarkarray. removeat (m );

}

} While (! Bnoneremoveed );

 

}

The principle of the above function is: constantly delete the pointer that the smart pointer is not included in the pointer of clockmarkarray until all the smart pointers of the pointer exist in the pointer of clockmarkarray.

The next step is how to unlock all the memory locked by each other. As a result, I introduced a parent class parent_autoptr to the smart pointer as follows:

Class parent_autoptr

{

Public:

Parent_autoptr ()

{Thisautomark = 0 ;}

Virtual ~ Parent_autoptr (){}

Virtual void release () {}// release pointer ownership

Protected:

Int thisautomark; // stores the smart pointer flag.

};

In the smart pointer, the function release () is overloaded.

Template <class T>

Class auto_ptr: Public parent_autoptr

{

Public:

Virtual void release () {remove ();}

Auto_ptr ()

{Mark = 0; pointee = 0; thisautomark = getautomark ();}

Auto_ptr (auto_ptr <t> & RHs );

Auto_ptr (T * PTR );

~ Auto_ptr () {remove ();}

T * operator-> () const;

Operator T *();

T & operator * () const;

Auto_ptr <t> & operator = (auto_ptr <t> & RHs );

Auto_ptr <t> & operator = (T * PTR );

PRIVATE:

Void remove ();

Int getautomark ();

Cmutex * getcmutex ();

Void readywrite ();

T * pointee;

Int mark;

};

The interlocked memory is released in cmarktable and cmark, as follows:

Void cmarktable: dolockmark ()

{

Getlockmark ();

Removeunlockmark ();

 

Int size = clockmarkarray. getsize ();

While (size)

{

Cmark * themark = (cmark *) clockmarkarray [0];

Clockmarkarray. removeat (0 );

If (themark)

{

Int size2 = (themark-> automarkarray). getsize ();

For (INT I = 0; I <size2; I ++)

{

Int automark = (themark-> automarkarray) [I];

Removegroup (automark );

}

Themark-> release ();

}

Size = clockmarkarray. getsize ();

}

 

Init ();

}

 

Void cmark: release ()

{

Int size = autoptrarray. getsize ();

For (INT I = 0; I <size; I ++)

{

Parent_autoptr * theptr = (parent_autoptr *) autoptrarray [I];

Theptr-> release ();

}

}

 

Now, it is a success. I immediately put it into testing and found that the job is very good. Even if 20 to 30 threads are opened, the program works well, no exception is thrown, and the garbage collection function is also very good. However, if there are too many threads, in cptrmanager, to ensure thread synchronization, it will cause a bottleneck effect. In serious cases, it will seriously affect the execution efficiency. At the same time, if every thread keeps locking the memory for life and death, garbage collection will be overwhelmed. After a long time, the system will be exhausted.

The code is easy to use. You only need to add the two files that I attached to the project, and then add the following code to your C * app:

Cptrmanager theptrmanager;

This will ensure that theptrmanager is parsed at the end of the process.

If you use it in a new project, this is enough. However, if you still need to use the original code, especially when passing pointer parameters, you must pay attention to it.

If you need to receive a pointer from the old Code and you need to release the pointer, you can use a smart pointer. If you do not need to release the pointer, you can only use a general pointer;

If you need to pass a pointer to the old Code and you need to release the pointer, you can use a smart pointer. Otherwise, you can only use a general pointer.

I will then attach all the source code. Since it has not been strictly tested, we 'd better test it again before using it. It is best to publish or write a letter to me about the problems found, I would like to express my gratitude.

You are welcome to download the test, criticize the correction and improvement,

Email: ydshzhy@263.net

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.