|
What's the difference
between debug mode and non-debug mode?
As far as the compiler is concerned or as far as EWin is concerned?
I will answer both questions.
As far as the compiler is concerned, "Debug mode"
is when the compiler disables all optimizations, and generates
code in an easy-to-debug format. That's all there is to that.
But the EWin wizard sets things up so that when you build the
"debug build", the special value DEBUG is defined.
And the EWin header files look for this and add in all sorts
of yummy things. Some of the functions do redundant safety checks
if DEBUG is defined. But more importantly, the ASSERT() macros
and LOGF() macros will only be compiled when DEBUG is on. At
other times, they are thrown away by the compiler. ASSERT() macros
are very handy.
So build and test your program in Debug Mode. (In MVC, you
decide which version to build by choosing Build|Set Active Configuration
from the menu.) And when you are about to release the product,
switch to Release build. Run it in Release mode once or twice
before you ship it off, by the way. Sometimes weird things can
creep in between builds.
What's
an ASSERT() macro? How do I use it?
An ASSERT() macro lets you assert that various things are
true. Say you have a function that takes an integer, and the
integer needs to be between 1 and 10.
|
|
void DoThing(int n)
{
ASSERT( (n >= 1) && (n <= 10) );
printf("%d\n", n);
}
|
If you call DoThing(11), a message box will pop up that says
'Assertion Failed.' It will also tell you the file name and line
number.
So why is ASSERT better than just writing an if-test yourself?
Two reasons. First, ASSERT() has more powers -- when the ASSERT
dialog pops up, you can have that dialog end your program, or
you can ignore the assertion, or you can have ASSERT crash your
program right then, so that you can more easily debug it.
And second, ASSERT() only exists if DEBUG is defined. DEBUG
is defined only when you are compiling in debug mode. So when
you compile the code in release mode, the entire ASSERT() line
is completely ignored by the compiler.
To put it another way: in Debug mode, DoThing() translates
into this:
|
|
void DoThing(int n)
{
(((n >= 1) && (n <= 10)) ? (void)0 : (void) __DoFailedAssert( NULL,
"(n >= 1) && (n <= 10)", whatever.c, 4 ) );
printf("%d\n", n);
}
|
Don't worry about what that hideous line DOES. Just compare
it to what the compiler sees when DEBUG is not #defined:
|
|
void DoThing(int n)
{
printf("%d\n", n);
}
|
See how that whole weird part goes away? That has two ramifications:
1. ASSERT() is nice and efficient -- when you make this in
Release Mode, all those extra if-tests and whatnot just go away.
2. ASSERT() completely goes away. That means you need to think
just a little bit before you write one. Say you wanted to ASSERT
that a particular function returned 5. You might write
|
|
void TestThing()
{
ASSERT( MyFunction() == 5 );
} |
That's nice in Debug mode. But in Release Mode, that translates
to
Nada. Nothing. The correct way to do that, then, would be
|
|
void TestThing()
{
int n = MyFunction();
ASSERT( n == 5 );
}
|
The moral is: Use assertions to test the parameters passed
to a function, the results of a computation, that sort of thing.
But never do important computation IN THE ASSERT ITSELF.
You may notice that if you compile the final version of TestThing()
above, you will get a warning in Release mode, that n is not
used. This is true: n never gets used in Release mode. So if
you want to avoid this warning, you would have to use n in some
way. Or else you would only use n in DEBUG mode:
|
|
void TestThing()
{
#ifdef DEBUG
int n =
#endif
MyFunction();
ASSERT( n == 5 );
}
|
This is ugly, but the only perfect way to avoid the error.
Don't worry too much, though; in reality you usually don't run
into this warning because you use the variable that the function
returns.
So that's ASSERT(). I highly recommend that you use them.
Just get into the habit for every new function you write. At
the beginning of the function, ASSERT() that all of the parameters
make sense. And if you do important calculations in the middle
of the function, ASSERT() that the resulting calculation is within
a valid range. It will really save you debugging time down the
line, trust me.
There are a few more versions of ASSERT(). The first that
I'll mention is ASSERTMSG(). This one is useful for when you
are writing libraries or code that will be used by other people.
It takes two parameters: the first is just like the parameter
you pass to ASSERT(), and the second parameter is a message that
the ASSERT box should display.
|
|
void TestThis(int n)
{
ASSERTMSG( n > 5, "The parameter n needs to be greater than 5 when"
"you call TestThis(n)");
}
|
Then, if they call TestThis(3), they will get a standard Assertion
Failed message, but it will also prominently display your text
string. In this particular case, the text string isn't too helpful
-- they can see that n should be greater than 5 because of the
"n > 5" part. But in this case:
|
|
void TestThis(int n)
{
ASSERTMSG(n != 2, "In TestThis(n), n cannot equal two because 2 is a"
"special constant that means something else.");
}
|
In general, use ASSERTMSG() when you want to give the user
of your code a little help in figuring out what went wrong or
why. Again, ASSERTMSG() is completely ignored by the compiler
in Release Build.
The last version of ASSERT is pretty arcane. It is called
ASSERT_RTTI. This one is handy for ASSERTing in a template. It
takes two parameters. The first is the same as in ASSERT(), and
the second is a variable of some type. The Assertion Failed dialog
will display the type name of that second parameter. To find
this name, it uses the new RTTI (Run Time Type Identification)
operator that was just recently added to C++, which is where
the "RTTI" part of the name comes from. If you are
porting EWin to an older compiler, this may give you trouble,
and you'll want to just rip it out or something. Better still,
get a new compiler.
So why is ASSERT_RTTI useful? In a template, as I said, when
you don't know what type of thing it's happening in. Here's a
template function that adds two variables of any type and then
returns the summed value. And for some made up reason, it will
ASSERT if the summed value is 6.
|
|
template <class T1, class T2> inline T1 Sum(T1 a, T2 b)
{
T1 x = a * b;
ASSERT_RTTI(x != 6, T1);
return x;
}
|
Now, if this ASSERTs, it will display something along the
lines of "Assertion Failed: x != 6 (Type is 'int'). File
blah blah Line xx." Where 'int' will be replaced with whatever
type T1 happens to be in that template.
What
does LOG do? What about LOGF? LOG1?
This is another very useful trick debugging trick: it writes
text messages to a console window. By calling
Your program will create a console window (if need be) and
will display that text, along with the file name and line number
that it originated from. So when you can't or don't want to trace
through your code, you can use LOG and its cousins to write debugging
information. And like ASSERT(), these are only displayed when
using the Debug Build of your program.
To pass parameters to the log, you can use LOGF. This is like
printf(), which is where it gets its name.
|
|
LOGF("The variable in question is currently set to %d", VarToDebug);
|
This is mighty handy. Unfortunately, LOGF should no longer
be used because it isn't thread safe. That is, if you have multiple
threads all calling LOGF at the same time, only one of the two
LOGFs will have the correct filename and line number. The other
one may get gibberish. Additionally, and more importantly, LOGF
doesn't completely go away when you use Release Build. A very
small portion of the code remains. It doesn't hurt anything,
but it makes your release program bigger. This is bad. And I
can't fix it -- these flaws are due to a limitation of the C
macro preprocessor.
So don't use LOGF. Instead, use LOG1, LOG2, LOG3, etc. Count
the number of extra parameters you want to pass, and use that
version:
|
|
LOG1("This has one other parameter: %d", n);
LOG2("%d %d", n, m);
LOG8("%d %f %s %d %f %d %d %d", n, s, r, t, w, q, z, p);
|
That sort of thing. These are thread-safe, and they also completely
remove themselves when you make a Release Build.
Oh, one other note: the data is sent both to a console window
and also to the "Debug" tab of MSVC. This is nice if
you want to see the LOG output after the program has ended (since
when the program ends, the console also goes away). You can also
view the output in several other ways, as explained in the next
section.
How do I fine-tune the debugging stuff?
So logging to a console is pretty handy, right? But logging
to a console can also be very slow. Sometimes you don't want
to write to a console. You may also want to send LOG messages
to a file, or to a socket, or to an external viewer. You can
do all this, plus other things. How? You set some flags, you
call some functions. An important thing to note is that all of
these functions only exist in Debug Build; in Release Build,
all these functions just mystically disappear, replaced with
empty macros that do nothing.
So the first thing to explain would be the various flags you
can set. These control all sorts of things. [Note that all the
flags turn things *OFF*. By default, all of these powers are
enabled.] You call SETDEBUGFLAGS(n), where n is a combination
of some of these flags:
|
|
//never write to a console window
#define DEBUGFLAG_NOCONSOLEWRITE 1
//never call OutputDebugString
#define DEBUGFLAG_NOOUTPUTDEBUGSTRINGWRITE 2
//never call MessageBox()
#define DEBUGFLAG_NOMESSAGEBOXWRITE 4
//don't watch to see if recursion occurs in __DoFailedAssert
#define DEBUGFLAG_NOTRACKINGRECURSIVEASSERT 8
//don't change colors in console windows
#define DEBUGFLAG_NOCOLORCONSOLETEXT 16
//never send messages to an external log-viewer
#define DEBUGFLAG_NOEXTERNALVIEWERWRITE 32
//when writing LOG/LOGF messages, don't truncate the filename path
#define DEBUGFLAG_NOTRUNCATEDFILEPATH 64
//never write LOG/LOGF messages to COM1
#define DEBUGFLAG_NOCOMLOG 128
//never write LOG/LOGF messages to COM1 or external logfile
#define DEBUGFLAG_NOLOGFILELOG 128
//assert messageboxes won't be application-modal
#define DEBUGFLAG_NOAPPMODALMSGBOX 256
|
Some of these are kind of arcane. I will go over the useful
ones.
DEBUGFLAG_NOCONSOLEWRITE. If you add this flag, you will not
get a little logging console. Data will never be sent there.
DEBUGFLAG_NOOUTPUTDEBUGSTRINGWRITE. If you add this flag,
LOG and its friends will never write data to the "Debug"
tab of MSVC.
DEBUGFLAG_NOMESSAGEBOXWRITE. If you add this flag, you will
never get popup dialogs, not even for failed ASSERT macros. You
will just get a little note in the log that an assertion failed.
This is mostly only useful when you are testing your program
via scripts, and you don't want unexpected message boxes to appear.
DEBUGFLAG_NOTRACKINGRECURSIVEASSERT. Very arcane. But it does
just as it says: it doesn't keep track of whether ASSERT() functions
happen recursively. Normally it does. I don't have any idea why
you would want to use this flag!
DEBUGFLAG_NOCOLORCONSOLETEXT. Hoo boy, this is arcane. But
there is a way to get color text in your output log. I will explain
how a little later. Anyway, this flag turns off colors.
DEBUGFLAG_NOEXTERNALVIEWERWRITE. Normally, you can use a simple
viewer program called LOGVIEW.EXE to view the debug messages.
By using this flag, you turn that power off. LOGVIEW is pretty
much like a console window, except that it is faster to write
to than a console window. Turn this off if you want LOG to be
ever-so-slightly-faster.
DEBUGFLAG_NOTRUNCATEDFILEPATH. Arcane. If you have very long
file names, LOG will truncate them to a nicer size so that the
output looks nicer. But you can turn truncation off via this
flag.
DEBUGFLAG_NOLOGFILELOG. Never write to a separate output file.
You can have LOG messages and whatnot sent to a separate file
via calling LOGFILEOPEN() as documented below. Setting this flag
causes no messages to be written to this file. It also stops
things from being written to the COM port, since the COM port
uses the same system as LOGFILEOPEN().
DEBUGFLAG_NOCOMLOG. Same as DEBUGFLAG_NOLOGFILELOG. See above.
DEBUGFLAG_NOAPPMODALMSGBOX. Arcane. Assertion Failed MessageBoxes
will not have the application-modal flag set.
So those are the flags. In addition to SETDEBUGFLAGS(), there
are some more debugging-tool functions:
SETDEBUGCONSOLESIZE(x,y) : this sets the size of the debugging
console to a given number of columns and rows. If this isn't
used, the "system defaults" are used.
SETDEBUGIDENTIFIER(x) : this sets the ID of this console.
This is theoretically usable by external viewer programs. However,
only one external viewer exists, LOGVIEW.EXE, and it does not
currently use this ID.
LOGFILEOPEN(fn) : opens the file fn and writes all logged
messages (and messages about failed assertions) to this file.
LOGFILECLOSE() : if you call LOGFILEOPEN(), you should call
this before your program ends, so that any buffered log messages
are written to the file.
LOGFILEWRITE(s) : directly writes a string to the file that
you opened via LOGFILEOPEN.
COMOPEN(fn), COMCLOSE(), COMWRITE(s) : similar to LOGFILEOPEN,
LOGFILECLOSE, and LOGFILEWRITE. In fact, they are the same functions,
internally. Instead of passing a file name, though, you pass
COMOPEN the string "COM1" or "COM2" or "COM3"
or "COM4".
COMSETSTATE(s) : sets the state of the COM port opened via
COMOPEN. This string should be in the format received by the
BuildCommDCB API function.
SOCKOPEN(fn), SOCKCLOSE(), SOCKWRITE(s) : same as the LOGFILE
functions and the COM functions above, but intended for use with
sockets. You pass SOCKOPEN() a string in the format "#127.0.0.1:3053"
but replacing 127.0.0.1 with any IP address, and 3053 with any
port number. This causes log messages to be sent as DATAGRAM
packets to that address & port number.
ENABLEDEBUGTYPE(), ENABLEALLDEBUGTYPES(), DISABLEDEBUGTYPE(),
DISABLEALLDEBUGTYPES(), ISDEBUGTYPEENABLED() : these are used
with the XLOG family of functions, described in the next and
final question about debugging.
Okay,
what is XLOG? How does it differ from LOG?
XLOG is like LOG, except that it takes a first parameter that
indicates what "type" of logging message it is. For
instance, if you had this:
|
|
LOG("Beginning matrix sequence now.");
|
You might decide to mark this as a "matrix" log
message:
|
|
XLOG("matrix", "Beginning matrix sequence now.");
|
When you write it out to the log, this type string will be
noted in the console. And similarly there are XLOGF, XLOG1, XLOG2,
etc. that mimic LOGF, LOG1, LOG2, etc., but that take an extra
parameter at the beginning.
By giving your log messages "types", you can turn
different types on and off at run-time, so that you only log
the data you want to see at that particular time. If you have
a dozen different types of log messages, you'll probably only
want to see one or two types at a time, as you try to debug those
systems. So in your OnInit() or wherever, you can disable or
enable the types you want.
By default, all types are enabled. The functions you can use
to enable or disable different types are:
|
|
ENABLEDEBUGTYPE(),
ENABLEALLDEBUGTYPES(),
DISABLEDEBUGTYPE(),
DISABLEALLDEBUGTYPES(). |
So you could disable all the types except for one particular
type via this code:
|
|
DISABLEALLDEBUGTYPES();
ENABLEDEBUGTYPE("matrix");
|
Note that type names are case sensitive.
And you can use ISDEBUGTYPEENABLED() to test if a type is
enabled. Note that this function always compiles to 0 (false)
in release mode.
And for weirdness points, there's this thing that allows colors:
if the type name starts with # and then two hex digits, messages
of that type name will be in color. For instance,
|
|
XLOG("#14DATA", "IMPORTANT DATA");
|
This will be red text on a blue background. The two-digit
number is evaluated as a hex number made up of the following
console-color flags:
|
|
#define FOREGROUND_BLUE 0x0001 // text color contains blue.
#define FOREGROUND_GREEN 0x0002 // text color contains green.
#define FOREGROUND_RED 0x0004 // text color contains red.
#define FOREGROUND_INTENSITY 0x0008 // text color is intensified.
#define BACKGROUND_BLUE 0x0010 // background color contains blue.
#define BACKGROUND_GREEN 0x0020 // background color contains green.
#define BACKGROUND_RED 0x0040 // background color contains red.
#define BACKGROUND_INTENSITY 0x0080 // background color is intensified.
|
(go on to
the next page in the tutorial...) |