The cstring class is a frequently used class, so it is necessary to analyze its memory management mode.
The evolution of cstring memory management is as follows:
Vc5 simply uses the new Delete method.
Because string operations require frequent memory size adjustment, the C ++ operator new and delete are used.
There is no function corresponding to realloc. The result is that every time the memory size is changed, additional resources are required.
Add a copy operation.
New and delete are allocated in the process heap in implementation. Frequent small memory allocation and release on the stack
A large number of fragments must be produced on the stack. Too many heap fragments directly affect program efficiency.
Therefore, MFC has been improved in vc6.
Vc6 uses new and delete to operate cstring in memory greater than 512 bytes and debug mode.
In release mode, memory pool management is used for Memory Allocation Operations of no more than 512 bytes.
And subdivided into four memory pools for management: <= 64, <= 128, <= 256, <= 512 bytes.
In this way, cstring has a good efficiency when it is no more than 512 bytes.
However, it is said that resolving a bug will produce the law of another bug.
Cstring cannot be avoided either.
So it was changed again in vc7.
Vc7 resumes using C's memory management call method. Alloc, free, and realloc. cstring
This is because new and delete do not have the realloc function to re-adjust the memory size. Previous problems lead to the final
The C management method is used. In vc6, to solve the performance problem of cstring small memory manipulation, MFC allocates memory of no more than 512 bytes in the release version.
Memory Pool Management is used for optimization. In other cases, cstring in new and delete. Release versions is used to call the following when processing memory of strings not greater than Bytes:
In vc6, the calling sequence of cstring memory allocation and memory release is as follows: cstring: allocbuffer
Cfixedalloc: alloc
Cplex: createcstring: freedata
Cfixedalloc: free
========================================================== ========================================================== ==========
The reference code is as follows:
File: mfcsrcstrcore. cpp
Void cstring: allocbuffer (INT nlen) // used to allocate memory
{
...
# Ifndef _ debug // In the release version and the value is no greater than 512 bytes
If (nlen <= 64)
{
Pdata = (cstringdata *) _ afxalloc64.alloc ();
Pdata-> nalloclength = 64;
}
Else is <= 1128, <= 256, <= 512
{
...
}
Else
# Endif // debug and release values greater than 512
{
Pdata = (cstringdata *)
New byte [sizeof (cstringdata) + (nlen + 1) * sizeof (tchar)];
Pdata-> nalloclength = nlen;
}
...
} Void fastcall cstring: freedata (cstringdata * pdata) // release the memory
{
# Ifndef _ debug is in the release version and cannot exceed 512 bytes
Int nlen = pdata-> nalloclength;
If (nlen = 64) // call the manager separately based on the memory size
_ Afxalloc64.free (pdata );
Else if (nlen = 128)
_ Afxalloc128.free (pdata );
Else if (nlen = 256)
_ Afxalloc256.free (pdata );
Else if (nlen = 512)
_ Afxalloc512.free (pdata );
Else
{
Assert (nlen> 512 );
Delete [] (byte *) pdata;
}
# Else // debug and release with a value greater than 512
Delete [] (byte *) pdata;
# Endif
} _ Afxalloc [64,128,256,512] is a global object of the cfixedalloc class. Let's analyze what problems occurs when cfixedalloc is used to manage the memory pool in an integral way?
Class cfixedalloc // defined in the mfcsrcfixalloc. h file
{
Public:
Cfixedalloc (uint nallocsize, uint nblocksize = 64 );
Uint getallocsize () {return m_nallocsize ;}
Public:
Void * alloc (); // allocation is called by cstring
Void free (void * P); // release is called by cstring
Void freeall (); // release all destructor calls
Public:
~ Cfixedalloc ();
Protected:
Struct cNode {// This is used to implement a one-way linked list
CNode * pnext;
};
Uint m_nallocsize; // the size of the object to be allocated is passed in only by the constructor.
Uint m_nblocksize; // The pre-allocated quantity, that is, the size of the pool, which is assigned by the constructor. The default value is 64.
Cplex * m_pblocks; // linked list pointer of the pool. The cplex object contains a cplex * pnext pointer object,
CNode * m_pnodefree; // the header pointer of the released block linked list, which should be regarded as a list of available memory blocks.
Critical_section m_protect; // critical area object
};/*
In the implementation of alloc, we can see that when there is no available block in the pool
Call cplex: Create to create a memory pool m_nallocsize * m_nblocksize
If there is one, a piece is popped up from m_pnodefree.
*/
Void * cfixedalloc: alloc ()
{
If (m_pnodefree = NULL) {// allocate a pool if no memory block is available
Cplex * pnewblock = NULL;
Try {// The allocated memory block defaults to 64 m_nallocsize.
Pnewblock = cplex: Create (m_pblocks, m_nblocksize, m_nallocsize );
} Catch_all (e ){
... Exception
} End_catch_all
// The following code pushes the memory block to the m_pnodefree linked list for use.
CNode * pnode = (cNode *) pnewblock-> data ();
(Byte * &) pnode + = (m_nallocsize * m_nblocksize)-m_nallocsize;
For (INT I = m_nBlockSize-1; I> = 0; I --, (byte * &) pnode-= m_nallocsize)
{
Pnode-> pnext = m_pnodefree;
M_pnodefree = pnode;
}
}
// A piece of memory is displayed for the caller.
Void * pnode = m_pnodefree;
M_pnodefree = m_pnodefree-> pnext;
...
Return pnode;
}
/*
When the caller calls free, the memory is only re-pushed into the m_pnodefree linked list.
It is not released, but marked as a usable block for future use.
*/
Void cfixedalloc: Free (void * P)
{
If (P! = NULL)
{
Entercriticalsection (& m_protect );
CNode * pnode = (cNode *) P;
Pnode-> pnext = m_pnodefree;
M_pnodefree = pnode;
Leavecriticalsection (& m_protect );
}
}
Void cfixedalloc: freeall ()
{
Entercriticalsection (& m_protect );
M_pblocks-> freedatachain ();
M_pblocks = NULL;
M_pnodefree = NULL;
Leavecriticalsection (& m_protect );
}
/*
Call freeall In the destructor to release the memory.
*/
Cfixedalloc ::~ Cfixedalloc ()
{
Freeall ();
Deletecriticalsection (& m_protect );
}
/*
Mfcincludeafxplex_.h
*/
Struct cplex // warning variable length structure
{
Cplex * pnext;
Void * Data () {return this + 1 ;}
Static cplex * Pascal create (cplex * & head, uint Nmax, uint cbelement );
Void freedatachain (); // free this one and links
};/*
Mfcsrcplex. cpp
*/
Cplex * Pascal cplex: Create (cplex * & phead, uint Nmax, uint cbelement)
{
Cplex * P = (cplex *) New byte [sizeof (cplex) + Nmax * cbelement];
P-> pnext = phead;
Phead = P; // Add to the linked list
Return P;
}
Void cplex: freedatachain () // free this one and links
{
Cplex * P = this;
While (P! = NULL ){
Byte * bytes = (byte *) P;
Cplex * pnext = p-> pnext;
Delete [] bytes;
P = pnext;
}
========================================================== ============================================
Now let's use an instance to look at the actual memory actions in the release version to allocate 10000 cstring arrays containing the "abcdefghijklmnopqrstuvwxyz" string cstring * strarray [10000]; for (Int = 0; I <10000; I ++)
Strarray [I] = new cstring ("abcdefghijklmnopqrstuvwxyz"); _ afxalloc64: alloc; callback is called because the string is smaller than 64 ;----------------------------------------------------------------
_ Afxalloc64 is defined in strcore. cpp as follows:
Afx_static cfixedalloc _ afxalloc64 (round4 (65 * sizeof (tchar) + sizeof (cstringdata); sizeof (tchar) = 1 in ANSI
Sizeof (cstrgindata) = 12;
65 * sizeof (tchar) + sizeof (cstringdata) = 77; Define round4 and round it to a multiple of 4,
# Define round (x, y) (x) + (Y-1 ))&~ (Y-1 ))
# Define round4 (x) round (x, 4)
So
_ Afxalloc64 (round4 (65 * sizeof (tchar) + sizeof (cstringdata) actually
The grand scale was finally launched
Extern cfixedalloc _ afxalloc64 (80, 64 );
---------------------------------------------------------------- Allocate the pool size in cplex
Sizeof (cplex) + Nmax * cbelement = 4 + 80*64 = 5124 byte. Because 10000 is not an integer multiple of 64 = 157 pools need to be allocated
Actually allocated memory = 157*5124 = 804468 byte = 804kb. Release the cstring object
For (Int = 0; I <10000; I ++)
Delete strarray [I]; in this case, cstring calls _ afxalloc64.free. the implementation of cfixedalloc: Free shows that the memory is not actually released at this time, but the block is re-added to the m_pnodefree linked list for use. because the cfixedalloc memory release operation is performed in the Destructor call, And _ afxalloc64 is defined as a global object. its destructor can be called only when the program exits. therefore, cfixedalloc can only be added but cannot be recycled before the end of the program. if we re-allocate 10000 strings> 64 <= 128 cstring objects
_ Afxalloc64 still occupies the memory, while _ afxalloc128 re-allocates 157*(4 + 144*64) = 157*9220 = 1447540 = 1.44754 MB
Then release it. The memory usage is 1.44754 MB + 804kb = 2.252008mb. compared with the char * object: char * charray [10000];
Allocate "abcdefghijklmnopqrstuvwxyz". The actual memory size is 27*10000 = 270kb.
Memory is recycled after being released
The amount of memory allocated to 128 strings is 10000*129 = 10000 MB.
Memory is recycled after being released
Conclusion:
The cstring in vc6 adopts the memory pool technology after improving the performance and heap fragmentation of small memory new and delete
Another memory leak is generated, which is not a memory leak. In fact, the cstring problem in vc5 and vc6 is caused by the fact that C ++ adopts the new and delete rules to manage the memory. In vc7, The cstring still returns to the C method.