3 * "Everyone makes errors, but with NoBug you won't make them twice!"
5 Nobug is a simple debugging library for instrumenting C and C++ programs
6 inspired from Design-by-Contract ideas.
10 NoBug provides you with the following:
12 * Three different levels for checks - in depth to final no-overhead
13 * Scope tags - tell whenever a function or loop is considered to be bug free
14 * Preconditional, Postcondition and Invariant checks, generic Assertions
15 * Debugger support (actions are only executed while running under a
16 debugger), currently only valgrind
17 * Dumping of your datastructures
18 * Logging your application's activities
19 * Runtime customizable logging via an enviromnet variable
20 * Different logging targets (stderr, syslog, debugger...)
21 * Annotation of your sourcecode about known bugs, things to do, etc.
22 * Tracking Resources (Files, Locks, etc.) used by your programm; help in
26 In contrast to traditional debuggers, NoBug is a non-interactive debugger which
27 is linked to your application doing hard-coded tests in a efficient,
30 Building and Installing
34 Release tarballs are attached to this wiki at:
36 * http://pipawiki/NoBug?action=AttachFile
38 I am using gpg signed tarballs for distribution. As first step one has to check
41 $ gpg nobug-X.Y.tar.gz.gpg
43 This will produce a nobug-X.Y.tar.gz and report if the signature could be
46 Since they are built with gnu autotools, the usual build and install procedure
49 $ tar xzvf nobug-X.Y.tar.gz
55 Development Version via git
57 The devlopment version is available via git from 'git://git.pipapo.org/nobug'
58 or mirrored at repo.or.cz 'git://repo.or.cz/nobug.git'.
60 After you cloned the repository you need to bootstap the autotools first
64 Then the usual ./configure && make && make install will work.
66 There is a special makefile target make meta to bring serveral files (README,
67 AUTHORS, NEWS) in sync with the NoBug Documentation wiki and update the
72 Currently, NoBug installs the following:
74 * A single nobug.h headerfile which your code will use
75 * Two libs for static linking:
76 + 'libnobug.a' for singlethreaded programs
77 + 'libnobugmt.a' for multithreaded programs.
81 You can make Nobug features available either by installing it as described
82 above or by shipping the nobug sources along with your project.
84 To use NoBug, some controling preprocessor macros have to be defined. The
85 nobug.h header should then be included.
87 A project using NoBug should use autoconf to check for execinfo.h and valgrind/
90 AC_CHECK_HEADERS([execinfo.h valgrind/valgrind.h])
92 For Multithreaded programs, you should also check for pthread.h.
94 When the resulting HAVE_PTHREAD_H, HAVE_EXECINFO_H and HAVE_VALGRIND_VALGRIND_H
95 are defined by the configure script, the corresponding features become
98 NoBug then defines 'NOBUG_USE_PTHREAD', 'NOBUG_USE_VALGRIND' and
99 'NOBUG_USE_EXECINFO' to 1. If you do not want to use any of these features in
100 NoBug, you can define these macros to 0 before including nobug.h.
102 If NVALGRIND is defined, valgrind support will not be used.
104 There are many other macros which can be set and overridden by the user to
105 control the behavior. Please edit the Documentation wiki when you find them
108 A program using NoBug should be linked against 'libnobug.a' if it is
109 singlethreaded or against 'libnobugmt.a' if it is multithreaded. The Library
110 must be initialized with NOBUG_INIT before using any other features or creating
111 threads. This is discussed in more detail at NoBug/Documentation/Macros/
118 Nobug uses the following different levels of checks:
121 + for expensive testing and logging while developing the software
123 + for testers and users who want to try the software
125 + finished version for end users
127 Release builds remove all assertions, but some logging is kept. We make the
128 assumption that bugs which where not covered in alpha and beta builds will not
129 easily show up in releases because the assertions there where not sufficent.
130 Further, end users are not test bunnies and will not provide good bug reports
131 anyway. If there is a problem in a release build, try to investigate the cause
132 with a beta build from the same source.
134 To define a debug level for compilation, just use '-DEBUG_ALPHA' or
135 '-DEBUG_BETA' for debug builds and use the standard '-DNDEBUG' for a release
136 build. Nobug will complain if the debug level has not been defined.
140 The programmer can tag any Scope as UNCHECKED or CHECKED. In ALPHA and BETA
141 builds, a global UNCHECKED is implied. In RELEASE builds, UNCHECKED Scopes are
146 Here is a table of which basic assertions gets checked on each level/scope
150 UNCHECKED Preconditions, Postconditions, Preconditions, compiling will
151 Invariants Postconditions abort
152 CHECKED Preconditions, Postconditions Preconditions
156 The NoBug interface is almost completely implemented in preprocessor macros.
157 This is needed because it uses the __FILE__ and __LINE__ macros for logging.
158 All of the flat namespace uppercase identifiers make it easily recognizeable
159 within source code too.
161 All macros are unconditionally available with NOBUG_ prefixed. For convenience,
162 there are also macros without this prefix as long as such a macro was not
163 already defined. For each Assertion and Logging macro, there is a form with the
164 suffix _DBG which will be only active under a debugger. The _DBG versions are
165 only enabled in alpha builds.
167 When NOBUG_DISABLE_SHORTNAMES is defined by the user, then only the NOBUG_
168 prefixed macros are available and the short ones will never be defined.
170 There are also assertions with an _IF suffix taking a 'when' parameter as first
171 argument. The following is an example:
173 * REQUIRE_IF(foo!=NULL, foo->something == constrained)
175 The assertion will only be performed when when is true. The debugger versions
176 are available as _IF_DBG prefixed macros.
180 when Assertion is only performed if expression 'when' is true at runtime
181 expr Test without side effects
182 fmt printf like formatstring
183 ... If not preceeded by 'fmt', then printf like formatstring. Otherwise,
185 flag Flag for enabling custom logging groups
186 type Type of the data to be checked as a single identifier name
187 pointer Pointer to type
189 depth Depth for invariants and dumps
199 before using any other NoBug feature. Probably in main or any other library
200 initialization routine. Calling NOBUG_INIT more than once is supported and each
201 subsequent call will be a no-op, thus initialization in main and in libraries
206 If you want to use environment variable controlled debuging, then you have to
207 initialize each flag with
209 * NOBUG_INIT_FLAG(flagname)
213 * NOBUG_INIT_FLAG_LIMIT(flagname, default)
215 This is documented later in NoBug/Documentation/Macros/LoggingConfiguration.
217 * <!> These two macros call NOBUG_INIT for backward compatibility.
221 In Multithreaded programs you should assign an identifier to each thread after
224 * NOBUG_THREAD_ID_SET(name)
226 If you don't call it, then NoBug will assign a automatic identifier. This is
227 documented in NoBug/Documentation/Macros/MultiThreading.
232 + Precondition (input) check. Use these macros to validate input a
233 function recieves. The checks are enabled in ALPHA and BETA builds and
234 optimized out in RELEASE builds.
237 + Postcondition (progress/output) check. Use these macros to validate the
238 data a function produces (example: return value). The checks enabled
239 unconditionally in ALPHA builds and optimized out in BETA builds for
240 scopes which are tagged as CHECKED. In RELEASE builds this checks are
241 always optimized out, but scopes tagged as UNCHECKED are not permitted.
244 + Generic check. Use these macros when you want to validate something
245 which doesn't fall into one of the above categories. A example is when
246 a library function can return a unexpected result (scanf with syntax
247 error in the formatstring, when a constant/literal formatstring is
248 expected). The checks are enabled in ALPHA and BETA builds and
249 optimized out in RELEASE builds.
252 + Nobug overrides the standard assert macro in ALPHA and BETA builds.
253 This is just a compatibility feature, its use is not suggested.
255 INVARIANT(type, pointer, depth)
256 + Checking invariants. You can provide more complex checking functions
257 which test the validity of datastructures. Invariants are only enabled
258 in ALPHA builds for scopes which are not tagged as CHECKED and
259 otherwise optimized out. (TODO: document how to write invariants)
263 Nearly all NoBug Macros emit some log message. NoBug gives the user fine
264 grained control over these log messages to display only interesting information
265 without loosing details.
267 Log messages are routed to different destinations which are:
270 + The underlying storage backend. Messages are appended to the end of the
271 buffer, overwriting older messages at the front of the buffer. NoBug
272 comes with a highly efficent ringbuffer implementation.
274 + This is either just stderr or if running under valgrind then valgrind
275 facilities to include messages into its log will be used.
277 + The user can open Files for log messages.
279 + Messages are send to the standard system logging daemon.
281 + There is a interface which allows the programmer to catch logmessages
282 and display them in a application defined way.
284 Each logmessage has a priority descibing its severity in the same way as syslog
287 All non-fatal messages are associated with a programmer defined flag describing
288 the source of the message (subsystem, module, ...).
290 Putting it all together: A user can define which source/flag shall be logged at
291 what priority level and to which destination. To make this all easier NoBug
292 tries to give reasonable defaults.
298 Each Log macro has a explicit or implicit Log-Level which correspondends to
299 syslog levels. Logging is only emitted when the messages is more servere than a
302 The defaults look like:
305 ringbuffer TRACE INFO NOTICE ringbuffer must always be most verbose
306 console INFO NOTICE -1 no log to console in release
307 file TRACE NOTICE WARNING
308 syslog -1 NOTICE WARNING no syslog for test runs
309 application INFO WARNING ERROR
311 Depending on the build level there is a default logging target and a default
312 limit which is choosen when the user doesn't specify one.
314 The default limits are:
316 * In ALPHA builds, NOBUG_LOG_LIMIT_ALPHA is used which defaults to LOG_INFO
317 * In BETA builds, NOBUG_LOG_LIMIT_BETA is used and defaults to LOG_WARNING
318 * In RELEASE builds, NOBUG_LOG_LIMIT_RELEASE is used and defaults to LOG_CRIT
320 The default targets are:
322 * In ALPHA builds, NOBUG_LOG_TARGET_ALPHA is used which defaults to
324 * In BETA builds, NOBUG_LOG_TARGET_BETA is used and defaults to
326 * In RELEASE builds, NOBUG_LOG_TARGET_RELEASE is used and defaults to
329 You can override all of those values with your own preference. Alternatively
330 NOBUG_LOG_LIMIT and NOBUG_LOG_TARGET can be defined by the user to override all
335 Flags are used to tell NoBug about subsystems/modules or even finer grained
338 A Flag should be declared with
340 * NOBUG_DECLARE_FLAG(flagname)
342 preferably in one of your headers
344 Further it must be defined with
346 * NOBUG_DEFINE_FLAG(flagname)
350 * NOBUG_DEFINE_FLAG_LIMIT(flagname, limit)
352 in one of your source files
356 * NOBUG_INIT_FLAG(flagname)
360 * NOBUG_INIT_FLAG_LIMIT(flagname, default)
362 once at the start of your program for every flag.
364 For flags defined with NOBUG_DEFINE_FLAG(flagname) the defaults are initialized
365 as in the table above, while NOBUG_DEFINE_FLAG_LIMIT(flagname, level) is used
366 to initialize the default target (depending on build level) to limit. Calling
367 NOBUG_INIT_FLAG(flagname) is optional for limit initialized flags.
369 NOBUG_INIT_FLAG(flagname) parses the environment variable '$NOBUG_LOG' for
370 flagname, the syntax is as following:
372 logdecl_list --> logdecl, any( ',' logdecl_list).
374 logdecl --> flag, opt(limitdecl, any(targetdecl))
376 flag --> "identifier of a flag"
378 limitdecl --> ':', "LIMITNAME"
380 targetdecl --> '@', "targetname", opt(targetopts)
382 targetopts --> '(', "options for target", ')', opt(targetopts)
384 Roughly speaking, NOBUG_LOG contains a comma separated list of declarations for
385 flags which are the name of the flag followed by a limit which is written in
386 all uppercase letters and preceeded by a colon, followed by target declarations
387 which are names of the targets, introduced by a at sign. Target declarations
388 can have options in future which is not yet implemented. Limit and target
389 declarations are optional and then choosen from the defaults table above. These
390 defaults are currently just an guess what should be useable and might be
393 <!> NOTE: '(options)' on targets are not yet implemented!
397 NOBUG_LOG='flag,other' # set the limit of the default target a default limit (see table above)
398 NOBUG_LOG='flag:DEBUG' # set the limit of the default target to DEBUG
399 NOBUG_LOG='flag:DEBUG@console@syslog' # set console and syslog limits for flag to DEBUG
400 NOBUG_LOG='flag:DEBUG,other:TRACE@ringbuffer'
402 There is a predefined flag NOBUG_ON which is always enabled.
407 2 NOBUG_DEFINE_FLAG (test);
412 7 NOBUG_INIT_FLAG (test);
414 9 INFO (test, "Logging enabled");
415 10 INFO (NOBUG_ON, "Always on");
420 $ tcc -DEBUG_ALPHA -run example.c
421 example.c:10: debug: INFO: main: Always on
423 $ NOBUG_LOG=test tcc -DEBUG_ALPHA -run example.c
424 example.c:9: debug: INFO: main: Logging enabled
425 example.c:10: debug: INFO: main: Always on
429 These macros log with implicit Log Level, note that there are no more servere
430 levels than LOG_ERROR since these should be handled by Assertions in a
433 * ERROR(flag, fmt, ...)
434 ERROR_IF(expr, rflag, fmt, ...)
435 + Application takes a error handling brach
438 WARN_IF(expr, flag, fmt, ...)
439 + Rare, handled but unexpected branch
442 INFO_IF(expr, flag, fmt, ...)
443 + Message about program progress
445 NOTICE(flag, fmt, ...)
446 NOTICE_IF(expr, flag, fmt, ...)
449 TRACE(flag, fmt, ...)
450 TRACE_IF(expr, flag, fmt, ...)
451 + Very fine grained messages
453 Note that TRACE correspondends to LOG_DEBUG, using 'DEBUG' could be ambiguous.
455 There is one generic LOG macro which takes the level explicitly:
457 * LOG(flag, lvl, fmt, ...)
458 LOG_IF(expr, flag, lvl, fmt, ...)
460 Dumping Datastructures
462 * DUMP(flag, type, pointer, depth)
463 + Dump a datastructure
464 DUMP_IF(expr, flag, type, pointer, depth)
465 + Dump datastructure if expr is true
467 How to write DUMP handlers
473 3 int INTEGER_MEMBER;
474 4 char * STRING_MEMBER;
475 5 struct STRUCTNAME* next;
478 then you define a function like:
481 2 nobug_STRUCTNAME_dump (const struct STRUCTNAME* self,
487 8 // check for self != NULL and that the depth
488 9 // limit did not exceed in recursive datastructures
489 10 if (self && depth)
491 12 // use DUMP_LOG not LOG to print the data
492 13 DUMP_LOG("STRUCTNAME %p: int is %d, string is %s", self,
493 14 self->INTEGER_MEMBER,
494 15 self->STRING_MEMBER);
495 16 // now recurse with decremented depth
496 17 nobug_STRUCTNAME_dump (self->next, depth-1, file, line, func);
500 now you can use the DUMP() macros within the code
504 3 struct STRUCTNAME foo;
506 5 DUMP (my_flag, STRUCTNAME, &foo, 2);
509 Dumping is by default done on level LOG_DEBUG, this can be overridden by
510 defining NOBUG_DUMP_LEVEL to some other level, further DUMP_IF is the only
511 enabled dumping macro for the RELEASE build level.
515 One can tagging features as:
518 + Something which shouldn't be used in future.
520 + important, not yet finished feature
522 + planned for future feature
524 + known bug to be fixed later
526 + enhancement to be done soon
528 + used to tag code-path which shall be never executed (defaults in switch
529 statements, else NOTREACHED after series of if's)
531 The advantage of this tagging over plain source comments is that we can take
532 some actions if we run in such a tag at compile or runtime:
534 the action to be taken when such a macro is hit depends on the build level:
537 DEPRECATED log nothing wont compile
538 UNIMPLEMENTED abort abort wont compile
539 FIXME log wont compile wont compile
540 TODO log log wont compile
541 PLANNED log nothing nothing
542 NOTREACHED abort abort removed
546 * abort means first log and then abort
547 * log will only log once for each sourceline (not on each hit)
548 * wont compile will abort compilation with a error message
549 * nothing optimized out, sane way
550 * removed optimized out for performance reasons
554 With little effort, NoBug can watch all kinds of resources a program uses. This
555 becomes useful for resources which are distributed over a multithreaded
556 program. Resource tracking is only active in ALPHA builds and optimized out in
557 BETA and RELEASE builds.
561 Resources are abstracted, NoBug has little knowledge about the semantics of a
562 resource, it only keeps records of resources and the code using it and ensures
563 basic constraints. Detailed usage checks of resource have to be done with other
566 Resources are identified by a arbitary identifier which is just a pointer and
567 if using threads, NoBug's thread identifier. Additionaly a name, the type and
568 the source locations which anounced the resource are stored.
570 Code which wants to use a resource calls a enter macro with its own identifier
571 and state, then might alter the state and finally a leave macro when finished
574 When a resource is used one has to pass one of three states:
576 * NOBUG_RESOURCE_WAITING
577 + For resources where acquisition could block (locks) you enter it with a
578 WAITING state and as soon you aacquired it you change the state to one
580 * NOBUG_RESOURCE_EXCLUSIVE
581 + Acquired the resource exclusively, this means for NoBug that trying to
582 get the resource again with the same identifier would be fatal, other
583 parts of the program could still enter it.
584 * NOBUG_RESOURCE_RECURSIVE
585 + The resource might be entered multiple times from the same identifier,
586 NoBug keeps a reference count and only deletes the record when the
587 resource is left as much times the state was changed to
588 NOBUG_RESOURCE_RECURSIVE
590 Possible state transistions:
596 Resources are accessed through handles, these handles are defined with
598 * RESOURCE_HANDLE(name)
600 This macro takes care that the declaration is optimized out in the same manner
601 the rest of the resource tracker would be disabled. You can still instantiate
602 handles as struct nobug_resource_record* in structures which must have a
603 constant size unconditional of the build level.
605 Resources are published by
607 * RESOURCE_ANNOUNCE(flag, type, name, identifier, handle)
609 Resources must be unique, it is a fatal error when a resource it tried to be
610 announced more than one time.
612 * 'flag' is nobug flag which turns logging on for this macro.
613 * 'type' is a cstring which should denote the domain of the resource, the
614 type "nobug" is reserved for internal use. Other strings are freely
615 chooseable, examples are "file" "mutex" "lock" "database" and so on.
616 * 'name' is the actual name of a named resource this is a cstring which
617 together with type forms a unique identifier of the resource. 'type' and
618 'name' must be available through the entire lifetime of the resource, using
619 literal strings is recommended.
620 * 'identfier' is a pointer which should be unique for this resource, any kind
621 of pointer will suffice, it is only used for identification. In
622 multithreaded applications the thread identifier becomes an additional
624 * 'handle' is a nobug_resource_record* which will be initialized to point to
625 the newly created resource.
627 When a resource becomes unavailable it is removed from the registry by
629 * RESOURCE_FORGET(flag, handle)
631 The resource must still exist and no users must be attacched to it, else a
632 fatal error is raised.
634 Code which wants to use a resource attaches to it by
636 * RESOURCE_ENTER(flag, announced, name, identifier, state, handle)
637 * 'flag' is nobug flag which turns logging on for this macro.
638 * 'announced' is the handle set by RESOURCE_ANNOUNCE
639 * 'name' is a free-form identifier
640 * 'identifier' is some pointer which must be unique for the user of the
642 * 'state' is the initial state, one of NOBUG_RESOURCE_WAITING,
643 NOBUG_RESOURCE_EXCLUSIVE or NOBUG_RESOURCE_RECURSIVE
644 * 'handle' is a nobug_resource_record* which will be initialized to the
647 For some uses one can change the state with
649 * NOBUG_RESOURCE_STATE(flag, entered, state)
650 * 'flag' is nobug flag which turns logging on for this macro.
651 * 'entered' is the handle set by RESOURCE_ENTER
652 * 'state' is the new state Note that only certain state transisitons are
653 allowed, see discussion/diagram above
655 When finished with using the resource you dissconnect from it either with the
656 handle you initialzed when entering it
658 * RESOURCE_LEAVE(flag, handle)
659 * 'flag' is nobug flag which turns logging on for this macro.
660 * 'handle' is the handle you got while entering the resource.
662 or by looking it up in the announced resource by its identifier
664 * RESOURCE_LEAVE_LOOKUP(flag, resource, identifier)
665 * 'flag' is nobug flag which turns logging on for this macro.
666 * 'resource' is the handle you got from announcing the resource.
667 * 'identifier' is the pointer you gave it when creating it
669 Resources keep track of the thread which used them, you cant ANNOUNCE/FORGET
670 ENTER/LEAVE the same handle from different threads.
672 Just a simple example:
674 1 NOBUG_DEFINE_FLAG_LIMIT(test, LOG_DEBUG);
679 6 // define a mutex and announce it
680 7 pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
681 8 RESOURCE_HANDLE(resource)
682 9 // 'example' is just a pointer to this function which suffices as unique id
683 10 RESOURCE_ANNOUNCE(test, "mutex", "my_mutex", example, resource);
685 12 // the following would be actually done in a different thread in a real program
686 13 RESOURCE_HANDLE(test, enter); // define a handle
687 14 RESOURCE_ENTER_NAME(test, resource, "example", &enter, NOBUG_RESOURCE_WAITING, enter);
688 15 // announce that we want to use the resource
689 16 // &enter also suffices as unique pointer
690 17 pthread_mutex_lock (&my_mutex); // this might block
691 18 RESOURCE_STATE(test, enter, NOBUG_RESOURCE_EXCLUSIVE); // we got it, now announce that
693 20 // program does something useful here
696 23 pthread_mutex_lock (&my_mutex); // and instantly unlock
697 24 RESOURCE_LEAVE(test, enter); // we don't need it anymore
699 26 // back in the main thread
700 27 RESOURCE_FORGET(test, resource); // remove the resource from the public registry
705 The Resource Tracker is able to detect potential deadlocks. This is done by
706 learning the relations between locks (precedence). A possible deadlock results
707 in a log message and a fatal abort. Note that only waiting on resources can
712 Unless the user defines NOBUG_RESOURCE_LOGGING to 0 each of the above macros
713 will emit a log message at NOBUG_RESOURCE_LOG_LEVEL which defaults to
718 The Resource Tracker is neither bullet-proof nor exact. There are small race
719 conditions in the time we announce/forget/enter/remove resources and doing the
720 actual call to a resource. These race conditions affect the reporting exactness
721 and are a design decision, for practical use they are no danger.
723 More important is that the Resource Tracker relies on that announce/forget and
724 enter/leave are properly paired. The programmer should ensure that this is done
725 right, else it will become unuseable. This will be fixed in future NoBug
728 The underlying resource-tracking library may report errors, all these errors
729 are fatal for NoBug and trigger an abort(). When such an error occurs the
730 Resource Tracker is left in a locked state, which is harmless and intended for
731 generating reports prior the abort.
735 It is important that NoBug protects certain operations with locks in
736 multithreaded programs. You have to ensure that 'HAVE_PTHREAD_H' is defined by
737 the configuration system and use the 'libnobugmt.a' library for linking. It is
738 particular important that libraries using NoBug are compiled with
739 'HAVE_PTHREAD_H' enabled when they are intended to be used in multithreaded
742 When Multithreading is used, log messages contain a identifier of the
743 orginating thread. This identifier should be set by
745 * NOBUG_THREAD_ID_SET(name)
747 right after thread creation. name is a string describing the purpose of the
748 thread. Nobug will assemble a unique identifier by appending a underscore and a
749 number to name, for example NOBUG_THREAD_ID_SET("gui") will result in a
750 identifier like "gui_5". When you don't set a thread identifier, then NoBug
751 assigns one automatically with the name 'thread' preprended if needed. Thread
752 identifiers are immutable once set.
754 For querying the thread identifier you use
756 * NOBUG_THREAD_ID_GET
758 which will return a const char* to the thread id in multithreaded programms and
759 a pointer to a literal "" in singlethreaded programs.
770 <!> this section is very work in progress
775 + Write a testsuite, build your program with -O0 -g -DEBUG_ALPHA and run
776 the testsuite under valgrind control. Hack until the program fits the
777 requirements defined by the testsuite.
779 + Build with desired optimization level and -g -DEBUG_BETA and give the
780 program to your beta testers.
782 + Just build it with optimization and without -g -DEBUG_*
784 What and when to check
786 * Add REQUIRE checks on your interfaces (incoming parameters). Especially if
787 a argument might not cover the whole range of the underlying type.
788 * Don't waste your and your CPU's time with unessesary checks. The testsuite
789 should validate your program. NoBug aids in debugging. You can add
790 Postconditions (ENSURE) and Invariants when you have a bug somewhere and
791 want to nail it down.
792 * Added checks don't need to be removed.
793 * When you use the CHECKED/UNCHECKED features then don't forget C scoping
794 rules, tag things as CHECKED from the leaves to the root.
798 * TRACE(flagname) or TRACE_DBG(flagname) at the begin of every nontrivial
799 function will easily log the progress of your application.
800 * Trying a RELEASE build will abort on certain conditions (known BUG, TODO's,
801 UNCHECKED code), you can use this to find these spots.
803 This Documentation is maintained at:
805 * http://www.pipapo.org/pipawiki/NoBug/Documentation
807 NoBug/Documentation (last edited 2007-06-16 23:15:33 by ct)