
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
Debugging Techniques
else if(names[i] == names[j]) // Superfluous - but it calls operator==() phrase = “ equal to “;
jName = new char[names[j].getNameLength()+1]; // Array to hold second name cout << endl << names[i].getName(iName) << “ is” << phrase
<< names[j].getName(jName);
}
}
cout << endl; return 0;
}
To reduce output further, you could switch off the trace output by commenting out the control symbols in the DebugStuff.h header:
// DebugStuff.h - Debugging control #pragma once
#ifdef _DEBUG |
|
|
|
//#define CONSTRUCTOR_TRACE |
// Output constructor call trace |
//#define FUNCTION_TRACE |
// Trace function calls |
#endif |
|
You can recompile the example and run it again.
How It Works
It works just as expected. You get a report that your program does indeed have memory leaks, and you get a list of the objects in the free store at the end of the program. The output generated by the free store debug facility starts with:
Detected memory leaks! Dumping objects ->
{143} normal block at 0x00355F08, 15 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD {142} normal block at 0x00355EC8, 15 bytes long.
Data: <Emily Steinbeck> 45 6D 69 6C 79 20 53 74 65 69 6E 62 65 63 6B {141} normal block at 0x00355E90, 12 bytes long.
Data: <Emily Miller> 45 6D 69 6C 79 20 4D 69 6C 6C 65 72
...
and ends with:
...
{120} normal block at 0x003559D8, 8 bytes long. Data: <Dickens > 44 69 63 6B 65 6E 73 00 {119} normal block at 0x003559A0, 8 bytes long. Data: <Charles > 43 68 61 72 6C 65 73 00
{118} normal block at 0x00355968, 11 bytes long. Data: <Ivor Horton> 49 76 6F 72 20 48 6F 72 74 6F 6E {117} normal block at 0x00355930, 7 bytes long. Data: <Horton > 48 6F 72 74 6F 6E 00
{116} normal block at 0x003558F8, 5 bytes long. Data: <Ivor > 49 76 6F 72 00
Object dump complete.
599

Chapter 10
The objects reported as being left in the free store are presented with the most recently allocated first, and the earliest last. It is obvious from the output that the Name class is allocating memory for its data members, and never releasing it. The last three objects dumped correspond to the pName array allocated in main(), and the data members of the object, myName. The blocks for the complete names are allocated in main(), and they too are left laying about. The problem our class has is that we forgot the fundamental rules relating to classes that allocate memory dynamically; they should always define a destructor, a copy constructor, and the assignment operator. The class should be declared as:
class Name |
|
|
{ |
|
|
public: |
|
|
Name(); |
|
// Default constructor |
Name(const char* pFirst, const char* pSecond); |
// Constructor |
|
Name(const Name& rName); |
|
// Copy constructor |
|
|
|
~Name(); |
|
// Destructor |
char* getName(char* pName) const; |
// Get the complete name |
|
int getNameLength() const; |
// Get the complete name length |
|
// Comparison operators for names |
|
|
bool operator<(const Name& name) const; |
|
|
bool operator==(const Name& name) const; |
|
|
bool operator>(const Name& name) const; |
|
|
|
|
|
Name& operator=(const Name& rName); |
|
// Assignment operator |
private: |
|
|
char* pFirstname; |
|
|
char* pSurname; |
|
|
}; |
|
|
You can define the copy constructor as:
Name:: Name(const Name& rName)
{
pFirstname = new char[strlen(rName.pFirstname)+1]; |
// Allocate space for 1st name |
strcpy(pFirstname, rName.pFirstname); |
// and copy it. |
pSurname = new char[strlen(rName.pSurname)+1]; |
// Same for the surname... |
strcpy(pSurname, rName.pSurname); |
|
} |
|
The destructor just needs to release the memory for the two data members:
Name::~Name()
{
delete[] pFirstname; delete[] pSurname;
}
600

Debugging Techniques
In the assignment operator, you must make the usual provision for the left and right sides being identical:
Name& Name::operator=(const Name& rName) |
|
{ |
|
if(this == &rName) |
// If lhs equals rhs |
return *this; |
// just return the object |
delete[] pFirstname;
pFirstname = new char[strlen(rName.pFirstname)+1]; // Allocate space for 1st name
strcpy(pFirstname, rName.pFirstname); |
// and copy it. |
delete[] pSurname; |
|
pSurname = new char[strlen(rName.pSurname)+1]; |
// Same for the surname... |
strcpy(pSurname, rName.pSurname); |
|
return *this; |
|
} |
|
You also should make the default constructor work properly. If the default constructor doesn’t allocate memory in the free store, you have the possibility that the destructor will erroneously attempt to delete memory that was not allocated in the free store. You need to modify it to:
Name::Name()
{
#ifdef CONSTRUCTOR_TRACE
// Trace constructor calls
cout << “\nDefault Name constructor called.”; #endif
// Allocate array of 1 for empty strings pFirstname = new char[1];
pSurname = new char[1];
pFirstname[0] = pSurname[0] = ‘\0’; |
// Store null character |
}
If you add statements to main() to delete the memory that is allocate dynamically there, the program should run without any messages relating to memory leaks. In main() you need to add the following statement to the end of the inner loop that is controlled by j:
delete[] jName;
You also need to add the following statement to the end of the outer loop that is controlled by i:
delete[] iName;
Finally, you still have to release the memory for pName after the loops in main():
delete[] pName;
601

Chapter 10
Debugging C++/CLI Programs
Life is simpler with C++/CLI programming. None of the complications of corrupted pointers or memory leaks arise in programs written for the CLR, so this reduces the debugging problem substantially compared to native C++ at a stroke. You set breakpoints and tracepoints in a CLR program exactly the same way as you do for a native C++ code. You have a specific option that applies to C++/CLI code for preventing the debugger from stepping through library code. If you select the Tools > Options menu item a dialog is displayed, and if you select the Debugging/General set of options the dialog looks as shown in Figure 10-19.
Figure 10-19
Checking the option highlighted in Figure 10-19 ensures that the debugger only steps through your source statements and executes the library code normally.
Using the Debug and Trace Classes
The Debug and Trace classes in the System::Diagnostics namespace are for tracing execution of a program for debugging purposes. The capabilities provided by the Debug and Trace classes are identical; the difference between them is that Trace functions are compiled into release builds, whereas Debug functions are not. Thus you can use Debug class functions when you are just debugging your code, and Trace class functions when you want to obtain Trace information in release versions of your code for performance monitoring or diagnostic and maintenance purposes. You also have control over whether the compile includes trace code in your program.
Because the functions and other members in the Debug and Trace classes are identical, I’ll just describe the capability in terms of the Debug class.
602

Debugging Techniques
Generating Output
You can produce output using the Debug::WriteLine() and Debug::Write() functions that write messages to an output destination; the difference between these two functions is that the WriteLine() function writes a newline character after the output whereas the Write() function does not. They both come in four overloaded versions; I’ll use the Write() function as the example but WriteLine() versions have the same parameter lists:
Function |
|
Description |
|
|
|
Debug::Write(String^ |
message) |
Writes message to the output destination. |
Debug::Write(String^ |
message, |
Writes categoryname followed by message to the |
String^ |
category) |
output destination. A category name is used to |
|
|
organize the output. |
Debug::Write(Object^ |
value) |
Writes the string returned by value->ToString() |
|
|
to the output destination. |
Debug::Write(Object^ |
value, |
Writes categoryname followed by the string |
String^ |
category) |
returned by value->ToString() to the output |
|
|
destination. |
|
|
|
The WriteIf() and WriteLineIf() are conditional versions of the Write() and WriteLine() functions in the Debug class:
Function |
Description |
|
|
Debug::WriteIf(bool condition, |
Writes message to the output destination if |
String^ message) |
condition is true; otherwise, no output is |
|
produced. |
Debug::WriteIf(bool condition, |
Writes categoryname followed by message to the |
String^ message, |
output destination if condition is true; |
String^ category) |
otherwise, no output is produced. |
Debug::WriteIf(bool condition, |
Writes the string returned by value->ToString() |
Object^ value) |
to the output destination if condition is true; |
|
otherwise, no output is produced. |
Debug::WriteIf(bool condition, |
Writes categoryname followed by the string |
Object^ value, |
returned by value->ToString() to the output |
String^ category) |
destination if condition is true; otherwise, no |
|
output is produced. |
|
|
As you see, the WriteIf() and WriteLineIf() functions have an extra parameter of type bool at the beginning of the parameter list for the corresponding Write() or WriteLine() function and the argument for this determines whether or not output occurs.
603

Chapter 10
You can also write output using the Debug::Print() function that comes in two overloaded versions:
Function |
Description |
|
|
Print(String^ message) |
This writes message to the output destination fol- |
|
lowed by a newline character. |
Print(String^ format, |
This works in the same way as formatted output |
...array<Object^>^ args) |
with the Console::WriteLine() function. The |
|
format string determines how the arguments that |
|
follow it are presented in the output. |
|
|
Setting the Output Destination
By default the output messages are sent to the output window in the IDE, but you can change this by using a listener, and a listener is an object that directs debug and trace output to one or more destinations. Here’s how you can create a listener and direct debug output to the standard output stream:
TextWriterTraceListener^ listener = gcnew TextWriterTraceListener( Console::Out);
Debug::Listeners->Add(listener);
The first statement creates a TextWriterTraceListener object that directs the output to the standard output stream, which is returned by the static property, Out, in the Console class. (The In and Error properties in the Console class return the standard input stream and standard error stream respectively.) The Listeners property in the Debug class returns a collection of listeners for debug output so the statement adds the listener object to the collection. You could add other listeners that additionally directed output elsewhere (to a file perhaps).
Indenting the Output
You can control the indenting of the debug and trace messages. This is particular useful in situations where functions are called at various depths. By indenting the output at the beginning of a function and removing the indent before leaving the function, the debug or trace output is easily identified and you’ll be able to see the depth of function call from the amount of indentation of the output.
To increase the current indent level for output by one (one indent unit is four spaces by default), you call the static Indent() function in the Debug class like this:
Debug::Indent(); |
// Increase indent level by 1 |
To reduce the current indent level by one you call the static Unindent() function:
Debug::Unindent(); |
// Decrease indent level by 1 |
The current indent level is recorded in the static IndentLevel property in the Debug class so you can get or set the current indent level through this property. For example:
Debug::IndentLevel = 2*Debug::IndentLevel;
This statement doubles the current level of indentation for subsequent debug output.
604

Debugging Techniques
The number of spaces in one indent unit is recorded in the static IndentSize property in the Debug class. You can retrieve the current indent size and change it to a different value. For example:
Console::WriteLine(L”Current indent unit = {0}”, Debug::IndentSize);
Debug::IndentSize = 2; |
// Set indent unit to 2 spaces |
The first statement simply outputs the indent size and the second statement sets it to a new value. Subsequent calls to Indent()increases the current indentation by the new size, which is two spaces.
Controlling Output
Trace switches provide you with a way to switch any of the debug or trace output on and off. You have two kinds of trace switches you can use:
The BooleanSwitch reference class objects provide you with a way to switch segment of output on or off depending on the state of the switch.
The TraceSwitch reference class objects provide you with a more sophisticated control mechanism because each TraceSwitch object has four properties that correspond to four control levels for output statements.
You could create a BooleanSwitch object to control output as a static class member like this:
public ref class MyClass
{
private:
static BooleanSwitch^ errors =
gcnew BooleanSwitch(L”Error Switch”, L”Controls error
output”);
public:
void DoIt()
{
// Code...
if(errors->Enabled) Debug::WriteLine(L”Error in DoIt()”);
// More code...
}
// Rest of the class...
};
This shows the errors object as a static member of MyClass. The first argument to the BooleanSwitch constructor is the display name for the switch that is used to initialize the DisplayName property and the second argument sets the value of the Description property for the switch. There’s another constructor that accepts a third argument of type String^ that sets the Value property for the switch.
The Enabled property for a Boolean switch is of type bool and is false by default. To set it to true, you just set the property value accordingly:
errors->Enabled = true;
605

Chapter 10
The DoIt() function in MyClass outputs the debug error message only when the errors switch is enabled.
The TraceSwitch reference class has two constructors that have the same parameters as the BooleanSwitch class constructors. You can create a TraceSwitch object like this:
TraceSwitch^ traceCtrl =
gcnew TraceSwitch(L”Update”, L”Traces update operations”);
The first argument to the constructor sets the value of the DisplayName property, and the second argument sets the value of the Description property.
The Level property for a TraceSwitch object is an enum class type, TraceLevel, and you can set this property to control trace output to any of the following values:
Value |
Description |
|
|
TraceLevel::Off |
No trace output. |
TraceLevel::Info |
Output information, warning, and error messages. |
TraceLevel::Warning |
Output warning and error messages. |
TraceLevel::Error |
Output Error messages. |
TraceLevel::Verbose |
Output all messages. |
|
|
The value you set determines the output produced. To get all messages, you set the property as follows:
traceCtrl->Level = TraceLevel::Verbose;
You determine whether a particular message should be issued by your trace and debug code by testing the state of one of four properties of type bool for the TraceSwitch object:
Property |
Description |
|
|
TraceVerbose |
Returns the value true when all messages are to be output. |
TraceInfo |
Returns the value true when information messages are to |
|
be output. |
TraceWarning |
Returns the value true when warning messages are to be |
|
output. |
TraceError |
Returns the value true when error messages are to be |
|
output. |
|
|
606

Debugging Techniques
You can see from the significance of these property values that setting the Level property is also set the states of these properties. If you set the Level property to TraceLevel::Warning for instance,
TraceWarning and TraceError is set to true and TraceVerbose and TraceInfo are set to false.
To decide whether to output a particular message, you just test the appropriate property:
if(traceCtrl->TraceWarning)
Debug::WriteLine(L”This is your last warning!”);
The message is output only if the TraceWarning property for traceCtrl is true.
Assertions
The Debug and Trace classes have static Assert() functions that provides a similar capability to the native C++ assert() function. The first argument to the Debug::Assert() function is a bool value or expression that causes the program to assert when the argument is false. The call stack is then displayed in a dialog as shown in Figure 10-20.
Figure 10-20
Figure 10-20 shows an assertion that is produced by the next example. When the program asserts the call stack presented in the dialog shows the line numbers in the code and the names of the functions that are executing at that point. Here there are four functions executing, including main().
You have three courses of action after an assertion. Clicking the Abort button ends the program immediately; clicking the Ignore button allows the program to continue; and clicking the Retry button gives you the option of executing the program in debug mode.
607

Chapter 10
You have three overloaded versions of the Assert() function available:
Function |
Description |
|
|
Debug::Assert(bool condition) |
When condition is false, a dialog displays |
|
showing the call stack at that point. |
Debug::Assert(bool condition, |
As above but with message displayed in the |
String^ message) |
dialog above the call stack information. |
Debug::Assert(bool condition, |
As the preceding version but with details |
String^ message, |
displayed additionally in the dialog. |
String^ details) |
|
|
|
Seeing it working is the best aid to understanding, so put together an example that demonstrates debug and trace code in action.
Try It Out |
Using Debug and Trace |
This example is just an exercise for some of the trace and debug functions I have described. Create a CLR console project and modify it to the following:
//Ex10_03.cpp : main project file.
//CLR trace and debug output
#include “stdafx.h”
using namespace System;
using namespace System::Diagnostics;
public ref class TraceTest
{
public:
TraceTest(int n):value(n){}
property TraceLevel Level
{
void set(TraceLevel level) {sw->Level = level; } TraceLevel get(){return sw->Level; }
}
void FunA()
{
++value;
Trace::Indent(); Trace::WriteLine(L”Starting FunA”); if(sw->TraceInfo)
Debug::WriteLine(L”FunA working...”); FunB();
Trace::WriteLine(L”Ending FunA”); Trace::Unindent();
608