?

Log in

 

Debugging - Advanced C++ Community

About Debugging

Previous Entry Debugging Aug. 13th, 2007 @ 07:02 am Next Entry
I've been thinking about ways to allow differing amounts of information to be displayed while debugging a program. And thus I came up with this possible solution. I would like to hear what other people have to think, and would greatly welcome any constructive criticism.



#ifdef DEBUG_FULL
#   define ASSERT_VERIFY(test, message)     do { assert((test) && (message)); } while(0)
#   define LOG_DEBUG(message)               do { Log(message); } while (0)
#   define LOG_VERIFY(test, message)        do { assert((test) && (message)); } while (0)
#   define THROW(ex, message)               do { assert(!(message)); } while (0)
#elseifdef DEBUG_EXTENDED
#   define ASSERT_VERIFY(test, message)     do { assert((test) && (message)); } while(0)
#   define LOG_DEBUG(message)               do { Log(message); } while (0)
#   define LOG_VERIFY(test, message)        do { if (!(test)) Log(message); } while (0)
#   define THROW(ex, message)               do { throw ex(message); } while (0)
#elseifdef DEBUG_REGULAR
#   define ASSERT_VERIFY(test, message)     do { assert((test) && (message)); } while(0)
#   define LOG_DEBUG(message)               do {} while (0)
#   define LOG_VERIFY(test, message)        do { if (!(test)) Log(message); } while (0)
#   define THROW(ex, message)               do { throw ex(message); } while (0)
#elseifdef DEBUG_RELEASE
#   define ASSERT_VERIFY(test, message)     do { if (!(test)) Log(message); } while(0)
#   define LOG_DEBUG(message)               do { Log(message); } while (0)
#   define LOG_VERIFY(test, message)        do { if (!(test)) Log(message); } while (0)
#   define THROW(ex, message)               do { throw ex(message); } while (0)
#else // release version
#   define ASSERT_VERIFY(test, message)     do {} while(0)
#   define LOG_DEBUG(message)               do {} while (0)
#   define LOG_VERIFY(test, message)        do { if (!(test)) Log(message); } while (0)
#   define THROW(ex, message)               do { throw ex(message); } while (0)
#endif


DEBUG_REGULAR would be used day to day during development. It has the following properties:
ASSERT_VERIFY will halt the program if the test fails.
LOG_DEBUG does nothing.
LOG_VERIFY logs any information if the test fails. This functionality would be used in the cases where a recoverable error occurs.
THROW throws the specified exception.

DEBUG_EXTENDED acts like DEBUG_REGULAR, except LOG_DEBUG now sends information to the log. This way, LOG_DEBUG can be used to send out state information that would normally clutter up the log.

DEBUG_FULL would be used when you want the application to halt when any error is detected.

DEBUG_RELEASE would be used when you want to track down an obscure bug, but you don't want to have the application arbitrarily halt. The major use of this would be to send it to end users, and have them record their actions while recreating a bug. ASSERT_VERIFY now acts like LOG_VERIFY

RELEASE would be the standard version to give to the public. ASSERT_VERIFY is deactivated since you don't want the program to simply halt. LOG_DEBUG is also deactivated

Leave a comment
[User Picture Icon]
From:omnifarious
Date:August 13th, 2007 12:21 pm (UTC)
(Link)

I see very little use for DEBUG_FULL as most exceptions represent conditions that are expected and will be handled appropriately at some point. Otherwise that looks interesting and potentially useful.

I'd not seen the use of the do { ... } while(0) construct to make a block look like a statement before. That's pretty useful for writing pre-processor macros.

The use of preprocessor macros makes me wonder though if there isn't a template based way of solving this problem.

[User Picture Icon]
From:tyrdinjer
Date:August 13th, 2007 01:29 pm (UTC)
(Link)
Sometimes, the location where an exception is caught is far away from from where it is thrown. So, DEBUG_FULL has the advantage of showing right where it happened. OTOH, I think most debuggers have the feature of break on throw...

The do {} while (0) construct is really nice. It takes care of a lot of problems.

The problem with using templates is that assert() is a bit odd. When assert halts the application it displays whatever is given to it as parameters. Not the values of the parameters, but the parameters themselves. Thus the following code:

bool someTest = (1==2);
const char *someMessage = "on noes!";
assert(someTest && someMessage);

Will generate the following message: failed assertion 'someTest && someMessage'.

Also, string literals cannot be used a parameter to a template. I.E. template< const char *message> cannot be used. So we cannot trick the message to appearing in the assert that way.
From:legolas
Date:August 13th, 2007 08:45 pm (UTC)
(Link)
What problem does this do while 0 construction solve exactly? (For my next macro maybe ;-)
[User Picture Icon]
From:tyrdinjer
Date:August 13th, 2007 09:33 pm (UTC)
(Link)
It makes the macro closer to be an actual function, and thus safe to be placed beneath a conditional. For example:

if(someCondition)
	SOME_MACRO();


If SOME_MACRO() was mutli-line, then all sorts of mysterious runtime errors could be created, even though your code compiles just fine.

To use the macros originally posted, I could do something like this:

if (someConditionThatMightBeCausingBugs)
	LOG_DEBUG("some important message");


Now, the if statement is always followed by a valid statement, even when the debug mode is such that LOG_DEBUG() is empty. This also means I don't have to put the if inside any special macros to make sure it is removed when I am not using it. Thus the code is cleaner.

Note, the optimizer will see that the macro does nothing, and thus eliminate both the macro and the if when removing useless code.
[User Picture Icon]
From:spasmaton
Date:August 13th, 2007 04:29 pm (UTC)
(Link)
You need the preprocessor because it's build settings dependent. You could have your debug settings as a template parameter (as is common for static asserts) but the resulting code would be a tangle and prone to breaking on some compilers, it would still depend on a #define somewhere and basically not worth it.

Re: exceptions, you're right and if you need to catch them your debugger will probably have a setting that can be enabled. In MS Visual Studio you can choose filters in Debug->Exceptions, likely similar on other platforms.
[User Picture Icon]
From:spasmaton
Date:August 13th, 2007 04:30 pm (UTC)
(Link)
^^^ yeh also what he said about the string pasting of the expression, that can be useful.
[User Picture Icon]
From:omnifarious
Date:August 13th, 2007 08:51 pm (UTC)
(Link)

I would be happy to use pre-processor trickery in just one place instead of everywhere a DEBUG macro was called. But you're right, there is no good way to get away from having at least some because it's build settings dependent.


Also, there's an answer to the assert and string pasting as well. The standard guarantees the __assert function is defined by assert.h. :-) But, I think that it wouldn't really work as a template somehow. :-/

[User Picture Icon]
From:tyrdinjer
Date:August 14th, 2007 04:25 am (UTC)
(Link)
Thanks for mentioning assert.h. I went and looked at it. And guess what __assert does? A call to printf() followed by abort(). Which means it would be possible to create more interesting classes to test asserts.

Alas, __FILE__ and __LINE__ still require a macro to use unless you want to include them explicitly everywhere they appear.

I'll have to think about this.
[User Picture Icon]
From:omnifarious
Date:August 14th, 2007 06:05 am (UTC)
(Link)

Oh, yeah, __FILE__ and __LINE__. *sigh* Now I remember trying to do something like this and being forced into using macros by those things.

I think I made a macro that constructed a temporary 'context' object that contained all the bits of context that could be gleaned at compile time. Of course, that macro had to be used everywhere you called the template because otherwise you'd just get the context where the macro was originally invoked and nowhere else.

Grrr... Oh, well. Sometimes you just have to use the preprocessor, namespace (keyword space really) pollution and all.

[User Picture Icon]
From:tyrdinjer
Date:August 14th, 2007 04:36 am (UTC)
(Link)
The more I think of it, the more it makes sense to take exceptions out of this. However, DEBUG_FULL will still have the effect that LOG_VERIFY() is turned into an assert.

The idea is that if recoverable error keeps popping up, you will notice it more, and thus will have an incentive to see if you can make the code more robust. This mode would be useful when you really want to stress test some code.
[User Picture Icon]
From:ataxi
Date:August 13th, 2007 10:28 pm (UTC)

Go aspect-oriented?

(Link)
Being able to configure (statically or dynamically) the behaviour of some cross-cutting concern (buzzword!) globally without altering it in each local execution point smells like aspect-oriented programming.

I've been really wishing C++ had a plan for AOP in its standardisation roadmap for the last little while, as the whole pissiness of cross-cutting concerns has been eating a lot into my time lately. Not that I really know how to use AspectC++ yet ...
[User Picture Icon]
From:tyrdinjer
Date:August 14th, 2007 04:18 am (UTC)

Re: Go aspect-oriented?

(Link)
I skimmed over the documentation at www.aspectc.org. Interesting concepts. And it does describe what I am doing here.

My only concern with formalized AOP is that it would bring another sub-language into C++. Right now C++ is practically three languages combined under one name: preprocessor macro language, template / meta language, and the core language. These three languages combine to give us some very powerful features, however that cost comes at a lot of complexity.

Sometimes I swear some of the code people generate is an attempt to write something for the obfuscated C contest.
[User Picture Icon]
From:ataxi
Date:August 14th, 2007 04:45 am (UTC)

Re: Go aspect-oriented?

(Link)
I agree that it's yet another layer of complexity in a language that's already complex, with a high marginal rate of increase of complexity :-)

Where I think AOP could really pay off is when you're doing exactly what you're doing here in a large, consistent pre-existing framework. Then the debug-logging AOP stuff gets done once right, in one place, and you can use it fairly unintrusively in your client code, with less obfuscation than most people currently have with #ifdeffed console prints and so forth (my colleagues are terrible with that stuff here). Dunno whether using it for concurrency is actually a good idea.
From:samildanach
Date:August 16th, 2007 02:28 pm (UTC)
(Link)
I would argue that ASSERTs should remain active when you release -- if you fail an ASSERT the alternative to terminating is, by definition, undefined behavior. Better to halt than to silently corrupt data.
[User Picture Icon]
From:tyrdinjer
Date:August 16th, 2007 11:45 pm (UTC)
(Link)
If you are trying to go the ultra robust route, turning the assert into an exception would be even better. The you can tell the user that something was wrong. With just an assert, the user will think the application just crashed.
(Leave a comment)
Top of Page Powered by LiveJournal.com