Macintosh Development |
[Home]
[About Us]
[People]
[Information Systems]
[Kerberos for Macintosh]
[Applications]
[Miscellaneous Documentation]
Assertions In DebuggingLib |
An assertion, or a signal, is code that verifies whether a condition that should always be true is satisfied, and interrupts the flow of the program if the condition fails. Assertions are a very useful debugging tool, because they reveal situations in which the program is not in a state it should be, thus exposing bugs that might be hard to find otherwise.
Assertions ae different from exceptions (which are, confusingly, also called signals in some texts; then there are UNIX signals too...), because assertions are a debugging aid, and exceptions are an error-handling mechanism.
A typical assertion would look like this: suppose that DeleteObject is a function that takes a non-nil pointer to an Object, and deletes it. Obviously, nil is not a valid input for that function, so we could write:
void
DeleteObject (
Object* inObject)
{
Assert_ (inObject != nil);
/* delete the object */
}
Of course, were DeleteObject designed to take nil pointers, this assertion would be unnecessary.
Common behavior of assertions is to display an error message, usually including some amount of debugging information. Since assertions can be computationally expensive, it is common to remove some assertions from final versions of programs, but leave them in debugging versions.
For additional general information about assertions (and other debugging techniques) you should refer to a good book, such as Steve Maguire's "Writing Solid Code". This section is by no means sufficient to describe all the good and useful things about assertions.
Whenever you can and it makes sense to do so.
If your code makes an assumption about its parameters, assert it. This is especially important for library developers, since they have no control over the parameters passed into the library, but they frequently need to make an assumption about the parameters passed, or place a restriction on the valid values of parameters. The above example is a good example of this technique.
If you write a piece of code that performs some algorithm which has some preconditions (conditions that are necessary for the algorithm to be valid) and postconditions (conditions that are guaranteed to be true if the algorithm is correct), assert them. For example, let MatrixAdd be a function that takes two matrices of the same size and returns their sum:
Matrix MatrixAdd ( Matrix inMatrix1, Matrix inMatrix2) { Assert_ ( (NumberOfRows (inMatrix1) == NumberOfRows (inMatrix2)) && (NumberOfColumns (inMatrix1) == NumberOfColumns (inMatrix2)); /* add the matrices */ }
Do not use assertions for error handling. If a combination of input parameters to your function is valid, but generates an error, don't assert; return an error code instead.
First, make sure you've prepared your targets and prefix files as described in How do I use DebuggingLib?
Then, add the following to your project prefix file:
#include <Debug.h>
Debug.h and some other header files require specific #include order to work; in particular, ansi_prefix.mac.h (if you are using it) must come before Debug.h in your prefix file.
If you are using PowerPlant, you can add Debug.h to your precompiler headers, but it has to come after UDebugging.h.
In your source files, you'll need:
#include <Debug.Assert.h>
You also need to add DebuggingLib.[68K|PPC][.debug] to your project.
If you are building a PowerPlant application, you should replace UDebugging.cp with DebuggingLib.[68K|PPC][.debug]. PowerPlant code will then automatically use the Debugging library (including getting the "Debug" button in assertion alerts), and you can use assertions in your code as described in the rest of this document.
Note: only versions 1.9.2 and 1.9.3 of PowerPlant are supported right now (these are versions of PowerPlant in CW Pro 4).
Glad you asked. For your basic assertion needs, use one of the following:
SignalIf_ (aCondition);
SignalIfNot_ (aCondition);
Assert_ (aCondition);
The meaning is self-explanatory: SignalIf_ will alert you to an unexpected condition if aCondition is true; SignalIfNot_ and Assert_ will alert you if aCondition is false.
The default behavior of assertions is to display an alert, both in debug and in final version. The alert is slightly different between two versions: in the debug version it has a button which lets you debug the application by dropping into Metrowerks debugger or MacsBug, and the alert in the final version has more user-friendly text.
There are several ways you can do more interesting things with assertions. In most cases, you won't have to use them, so this section is entirely optional.
In some cases you might want to assert with a specific message, which may not correspond to some useful boolean condition. You can do that using:
SignalPStr_ (inPascalString);
SignalCStr_ (inCString);
These two alert you immediately, but the message displayed in the alert/debugger message contains the specified string, instead of a quoted boolean condition.
The argument to SignalPStr_
can be either a string
literal (text between quotes) or a pascal string variable. The
argument to SignalCStr_
must be string literal, and
cannot be a char*
(this is because the macro uses
preprocessor concatenation to convert the string to a Pascal string).
For example:
if (lateInTheEvening) {
Sleep ();
} else
SignalPStr_ ("\pIt's too early to sleep. Tool some more.");
If you need to specify the behavior an assertion will have, you can use the following two:
SetSignalAction_ (inSignalAction);
RestoreSignalAction_ ();
Because of the way these are defined, you must put the call to
SetSignalAction_
within, or immediatelly before or
after, the declarations section of the block for which you want to
have an alternate behavior:
{
SetSignalAction_ (signalAction_SourceDebugger);
long i;
long j = 0;
/* Do something */
RestoreSignalAction_ ();
}
You absolutely must (and I mean it) restore the changes you
made, except when you are writing an application and the call to
SetSignalAction_
is at the beginning of your main().
The valid values of inSignalAction
, and their
meanings, are:
Value |
Behavior in debug version |
Behavior in final version |
|
Nothing happens. The assertion/signal is ignored. You shouldn't use this setting unless you are absolutely sure you want to ignore signals in a section of code. |
Nothing happens. The assertion/signal is ignored. You shouldn't use this setting unless you are absolutely sure you want to ignore signals in a section of code. |
|
An alert is displayed with the message consisting of the text of the signal condition, and file and line number where the assertion ocurred. The alert will have Quit, Ignore, and Debug buttons. Quit button kills the application, Ignore button ignores the assert, and Debug button drops into Metrowerks debugger. If you don't have Metrowerks debugger running, you may crash if you click Debug. |
Same as |
|
Control is transferred to Metrowerks debugger. If you don't have Metrowerks debugger running, you may crash. |
Nothing happens. The assertion is ignored. |
|
Control is transferred to MacsBug. If you don't have MacsBug installed and loaded, you may crash. |
Nothing happens. The assertion is ignored. |
|
Similar to |
Similar to |
Almost never. There are two important situations when you might want to:
If you find yourself debugging some code, and you want to change
the assertion behavior from that point on, you must change the value
of the global variable gSignalAction
. (Of course, all
standard warnings about changing variables in your program apply.)
The debugging library uses this value to determine the runtime
behavior of assertions, and you can use the debugger to set the value
of this variable to any of the values given in the table above. This
is, in fact, what the SetSignalAction_
macro does, but
you shouldn't rely on that in your code.
There may be some cases when you cannot allow arbitrary behavior inside a code segment (for example, you should not allow alerts from an interrupt handler or CFM initialization function). This is not an issue in most cases, but for the rare cases when this must be done, you can use the macros described above.
Since assertions can be computationally expensive, it is frequently desirable to strip some of them from final versions. On the other hand, it's better for an application to display a nice alert than to lock up or otherwise irritate users. Hence, assertions have three levels of compile-time support:
|
All assertions/signals are compiled into the code. Runtime behavior corresponds to the debug column of the previous table. |
|
Only assertions/signals not marked as computationally expensive are compiled into code. Runtime behavior corresponds to the final column of the previous table. |
|
No assertions/signals are compiled into code. |
You can change this option by using one of:
#define SignalMode_ SignalMode_All_
#define SignalMode_ SignalMode_Inexpensive_
#define SignalMode_ SignalMode_None_
From that point on, your asserts will be compiled according to the option you specify.
Given the above, you are probably wondering how you should mark your expensive assertions, so that they can be removed from your final version. You can use
#if SignalMode_ == SignalMode_All_
#endif /* SignalMode_ */
to achieve that. For example:
#if SignalMode_ == SignalMode_All_
Assert_ (CalculateSomethingHorriblyComplicated () == noErr);
#endif /* SignalMode_ */
This assertion will be removed from your final version, and so your final version won't carry the computational overhead.
In some cases, you might need to assert based on the result of some complex expression; in fact, the expression might be so complex that you can't write it in a single conditional. In this case, you should bracket that part of your code inside
#if SignalMode_ == SignalMode_All_
#endif /* SignalMode_ */
or
#if SignalMode_ != SignalMode_None_
#endif /* SignalMode_ */
depending on whether you want the statements that comprise the assertion to be in the final version or not.
I also recommend you put those blocks of code inside {} braces; that way you will reduce the chance of accidentally introducing dependencies between your debug and final code (see more about this in Caveats). For example:
#if SignalMode_ == SignalMode_All_
/* The sum must be non-zero */
{
int i;
int theSumOfEyes = 0;
for (i = 0; i < 1000; i++) {
theSumOfEyes += DoSomething (i);
}
Assert_ (theSumOfEyes != 0);
}
#endif /* SignalMode_ */
Assertions can be tricky. Assertions are based on preprocessor macros. This implementation of assertions also uses a global variable. Therefore, there are some things you can do to shoot yourself in the foot. Assuming you are not very keen to do that (maybe because someday you'll miss and shoot everybody else, at which point we'll hunt you down and make you understand the layout of the macathena locker), here are some ways to avoid when using assertions:
This is a bad way to assert:
Assert_ ((myHandle = NewHandle (inSize)) == nil);
When this code is compiled with assertions turned off,
myHandle
will never be allocated. Boom.
The assertions in your code should almost always rely on normal flow of your code, by checking the values your code normally produces, or paths your code normally takes. In rare cases, you might want to use an alternate algorithm to double-check the result your code normally produces. In no case should your assertions (and debugging code in general) perform anything that your final code doesn't. (This is really just a generalization of the first caveat.) Consequences of failing to follow this rule can range from minor annoyance to horrible mess, if, for example, you don't test your final version well enough and you later get reports that the functionality is not as you expected it to be. Take only pictures, leave only fotprints.
If you change the assertion behavior, always restore it to its
original value. Be especially careful with continue, break, return,
and goto statements, as they break the linear flow of your program,
and you could accidentally skip over a
RestoreSignalAction_
with them. Failure to comply is
hazard for your (and other people's) health.
Questions or comments? Send mail to macdev@mit.edu
Last updated on $Date: 2003/11/19 20:49:47 $
Last modified by $Author: smcguire $