📄 9new.html
字号:
void Dump ();
static bool <b>Ready</b>;
private:
void lock () { _lockCount++; }
void unlock () { _lockCount--; }
private:
std::map<void *, Entry> _map;
int _lockCount;
};</pre>
</td></tr></table><!-- End Code -->
<p>We have defined two auxillary classes, <var>Tracer::Entry</var> which is used as the value for the map, and <var>Tracer::Lock</var> which is used to temporary disable tracing. They are used in the implementation of <var>Tracer::Add</var> and <var>Tracer::Remove</var>.
<p>The method <var>Add</var> adds a new entry to the map, but only when tracing is active. Notice that it disables tracing when accessing the map--we don't want to trace the allocations inside the map code.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void Tracer::Add (void * p, char const * file, int line)
{
if (_lockCount > 0)
return;
Tracer::Lock lock (*this);
_map [p] = Entry (file, line);
}</pre>
</td></tr></table><!-- End Code -->
<p>The method <var>Remove</var> makes the same preparations as <var>Add</var> and then searches the map for the pointer to be removed. If it's found, the whole entry is erased.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void Tracer::Remove (void * p)
{
if (_lockCount > 0)
return;
Tracer::Lock lock (*this);
iterator it = _map.find (p);
if (it != _map.end ())
{
_map.erase (it);
}
}</pre>
</td></tr></table><!-- End Code -->
<p>Finally, at the end of the program, the method <var>Dump</var> is called from the destructor of <var>Tracer</var> to display all the leaks.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>Tracer::~Tracer ()
{
Ready = false;
Dump ();
}
void Tracer::Dump ()
{
if (_map.size () != 0)
{
std::cout << _map.size () << " memory leaks detected\n";
for (iterator it = _map.begin (); it != _map.end (); ++it)
{
char const * file = it->second.File ();
int line = it->second.Line ();
std::cout << file << ", " << line << std::endl;
}
}
}</pre>
</td></tr></table><!-- End Code -->
<p><i>Notice: if your implementation of standard library cannot deal with standard output after the termination of main (), read the next section, Debug Output.</i>
<p>Since we are overloading global operators <var>new</var> and <var>delete</var>, the <var>Tracer</var> has to be a global object too.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>extern Tracer NewTrace;</pre>
</td></tr></table><!-- End Code -->
<p>Notice that this might lead to some problems, if there are other global objects that allocate memory in their constructors. The order of construction of global objects residing in different files is undefined. If a memory-allocating global object is constructed before the construction of <var>NewTracer</var>, we're in trouble. That's why I introduced a static Boolean flag, <var>Tracer::Ready</var>, which is originally set to <var>false</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>bool Tracer::Ready = false;</pre>
</td></tr></table><!-- End Code -->
<p>The constructor of <var>Tracer</var> sets this flag to <var>true</var> and <var>Tracer::Dump</var> sets it back to <var>false</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>Tracer::Tracer ()
: _lockCount (0)
{
Ready = true;
}</pre>
</td></tr></table><!-- End Code -->
<p>The implementation of the overloaded <var>new</var> is straightforward.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void * operator new (size_t size, char const * file, int line)
{
void * p = malloc (size);
if (Tracer::Ready)
NewTrace.Add (p, file, line);
return p;
}</pre>
</td></tr></table><!-- End Code -->
<p>Notice that we use the low level memory allocating function <var>malloc</var>, rather than calling operator <var>::new</var>. That's because we are going to overload the regular new as well.
<p>There must be a corresponding overload of <var>delete</var>, to be used during exception unwinding.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void operator delete (void * p, char const * file, int line)
{
if (Tracer::Ready)
NewTrace.Remove (p);
free (p);
}</pre>
</td></tr></table><!-- End Code -->
<p>Since we used <var>malloc</var> for memory allocation, we have to use <var>free</var> for deallocation.
<p>For completeness, we also override the regular global <var>new</var>, in case there are parts of our code outside of the reach of macro substitution (for instance, parts of the standard library).
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void * operator new (size_t size)
{
void * p = malloc (size);
if (Tracer::Ready)
NewTrace.Add (p, "?", 0);
return p;
}</pre>
</td></tr></table><!-- End Code -->
<p>Finally, we have to override the global <var>delete</var> in order to trace <i>all</i> deallocations.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void operator delete (void * p)
{
if (Tracer::Ready)
NewTrace.Remove (p);
free (p);
}</pre>
</td></tr></table><!-- End Code -->
<p>Since we only want the tracing to be enabled in the debug version of our program, we'll enclose the definition of our macro in conditional compilation directives.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>#if !defined NDEBUG
#include "debugnew.h"
#define new new(__FILE__, __LINE__)
#endif</pre>
</td></tr></table><!-- End Code -->
<p>Most compilers define the flag <var>NDEBUG</var> (no debug) when building the release (non-debugging) version of the program. The file debugnew.h contains, among others, the declaration of overloaded operators new and delete.
<p>Similarly, we have to make sure that the implementation of overloaded <var>new</var> and <var>delete</var> is also compiled conditionally. That's because the decision as to which version of <var>new</var> and <var>delete</var> will be called from your program is done by the linker. If the linker doesn't find an implementation of these operators in your code, it will use the ones privided by the runtime library. Otherwise it will call your overrides throughout.
<p>Finally, we add the definition of the global object NewTrace to main.cpp. The destructor of this object will dump memory leaks after the end of <var>main ()</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>#if !defined NDEBUG
Tracer NewTrace;
#endif
</pre>
</td></tr></table><!-- End Code -->
<h4>Debug Output</h4>
<p>An even better idea is to redirect the dump to the debugger. (An additional advantage of doing that is to bypass potential library bugs that prevent standard output after <var>main ()</var>). There is a function <var>OutputDebugString</var>, declared in <windows.h>, which outputs strings to the debug window, if you are running the program under the debugger. We format the string using <var>std::stringstream</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>if (_map.size () != 0)
{
OutputDebugString ("*** Memory leak(s):\n");
for (iterator it = _map.begin (); it != _map.end (); ++it)
{
char const * file = it->second.File ();
int line = it->second.Line ();
int addr = reinterpret_cast<int> (it->first);
std::stringstream out;
out << "0x" << std::hex << addr << ": "
<< file << ", line " << std::dec << line << std::endl;
OutputDebugString (out.str ().c_str ());
}
OutputDebugString ("\n");
}</pre>
</td></tr></table><!-- End Code -->
<p>If your standard library doesn't handle even that, try bypassing integer output by using a low-level conversion routine, <var>itoa</var> (integer-to-ascii).
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre> char buffer1 [10];
char buffer2 [8];
out << "0x" << itoa (addr, buffer1, 16) << ": "
<< file << ", line " << itoa (line, buffer2, 10) << std::endl;
</pre>
</td></tr></table><!-- End Code -->
<h4>Placement new</h4>
<p>There is one particular overload of <var>new</var> that is part of the standard library. It's called <i>placement new</i> (notice: sometimes all overrides of <var>new</var> that take extra arguments are called <i>placement new</i>) and it takes one additional argument--a void pointer. It is used whenever the memory for the object has already been allocated or reserved by other means. The argument could be a pointer to some static memory or to a chunk of pre-allocated raw dynamic memory. Placement new does not allocate memory--it uses the memory passed to it (it's your responsibility to make sure the chunk is big enough) and calls the appropriate constructor.
<p>For instance, in our earlier example with bulk allocation, we could use placement <var>new</var> to create a <var>Block</var> object using memory that's been allocated as an array of bytes.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>char * p = ::new char [sizeof (Block) + BlockLinks * sizeof (Link)];
Block * block = <b>new (p)</b> Block (_blocks);
_blocks = block;</pre>
</td></tr></table><!-- End Code -->
<p>It makes sense now to have a constructor of <var>Block</var> that initializes the pointer to next. In fact, the method <var>SetNext</var> is no longer needed.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>LinkAllocator::Block::Block (Block * next) : _next (next) {}</pre>
</td></tr></table><!-- End Code -->
<p>The standard library defines a corresponding placement <var>delete</var> which does absolutely nothing, but is required in case the constructor throws an exception. Since placement <var>new</var> doesn't allocate any memory, it's an error to delete the object created by it. Of course, the <i>raw</i> memory that's been passed to placement <var>new</var> has to be dealt with appropriately. In our example, it's the <var>Purge</var> method that frees raw memory.
<p>By the way, there is also an <i>array</i> placement operator <var>new[]</var> and the corresponding <var>delete[]</var>. It is left as an exercise for the user to use it for converting memory following the <var>Block</var> header to an array of <var>Links</var> (what kind of a constructor would you add to <var>Link</var> for that purpose?).
</td>
</tr>
</table>
<!-- End Main Table -->
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -