Link to home
Start Free TrialLog in
Avatar of riceman0
riceman0

asked on

How to make my debug macros very concise

Hi, I have a debug routine that I call if certain define statements are enabled, for example:

#define TEST_ECHO_OUTPUT 1

void my_main_code()
{
  int a;
  a =1;

  #if TEST_ECHO_OUTPUT
  my_debug_out(a);
  #endif


}

#if TEST_ECHO_OUTPUT
void my_debug_out(int x)
{
...
}
#endif

This is all well and good, but those calls are ugly, is there a way to tighten up my macro "footprint" to one line, perhaps by defining a multiline macro?  For example:


#define TEST_ECHO_OUTPUT 1
#begin_multi_line_define TEST_ECHO_MACRO(x)  // made-up, but you see what I'm going for
#if TEST_ECHO_OUTPUT
my_debug_out(#x);
#endif
#end_multi_line_define

void my_main_code()
{
  int a;
  a =1;

  TEST_ECHO_MACRO(a)

}


This would be sweet, I have these test statements all over the place (by requirement) and if I could divide the footprint by 3 it would greatly beautify my code.  Note that when my test code is #defined away, I don't even want extra procedure calls (and stack-pushing), even if they don't do anything (which rules out the possibility of #defining out the guts of a real procedure call, I want to #define out the call itself...)

thought I'd ask in case you gurus have any tricks.  Thanks!
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi riceman0,

The macros involved are a bit cumbersome.  Perhaps better is to call the function and let it do an immediate return if debugging is disabled.

Another solution is to encapsulate the function call

#define MY_DEBUG_OUT(a) {if (TEST_ECHO_OUTPUT) my_debug_out(a);}

Good Luck,
Kent
Avatar of riceman0
riceman0

ASKER


Sorry, what do you mean "expands to nothing" exactly?  Literally, blank space?

(Being sure because I will need to defend my decision to superiors.)

>>Sorry, what do you mean "expands to nothing" exactly?  Literally, blank
>>space?

Yes, exactly. The preprocessor will just remove the line if TEST_ECHO_OUTPUT is not set.
Kdo: yeah I thought about using a function that does nothing if test mode not #defined (see my last parenthetical), but that is an extra procedure call that I don't want to do.

And for #define MY_DEBUG_OUT(a) {if (TEST_ECHO_OUTPUT) my_debug_out(a);}, I think that's a logical test that is left over if test mode not #defined, isn't it?  Don't want that either, my superiors would tell me to live with the three-line calls.

Actually

#define MY_DEBUG_OUT(a) {if (TEST_ECHO_OUTPUT) my_debug_out(a);}

will cause a aompiler error if TEST_ECHO_OUTPUT is not set, I am not sure that this is what you want...

jkr: true, although I was planning on setting TEST_ECHO_OUTPUT to either 1 or 0.

But your comment makes me wonder... a more experienced coworker does it differently:

#define TEST_ECHO_OUTPUT // comments this out to disable

#ifdef TEST_ECHO_OUTPUT

whereas I do this:

#define TEST_ECHO_OUTPUT  1 // change to 0 to disable

#if TEST_ECHO_OUTPUT

Is his approach more traditional?  Does it result in tighter code?  Is there a disadvantage to my approach, other than the danger of omitting a #define altogether and breaking the code (as you point out)?

(Thanks.  Sorry to piggy back this question, but I seem to have expert attention.)
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>>but why did you use the stringization operator ??

Because it was used in the Q in

my_debug_out(#x);

and would be required for

  my_debug_out(a);

I'd also rather not use it.

Do you mean the "#"?  My compiler documentation tells me that's how you pass parameters to a macro.  If I have that wrong, I will figure that out momentarily...
Re-reading the compiler doc, I shouldn't be using the "#".  Thanks for the heads-up.
>>My compiler documentation tells me that's how you pass parameters to a
>>macro.

No, not really:

The number-sign or stringizing operator (#) converts macro parameters (after expansion) to string constants. It is used only with macros that take arguments. If it precedes a formal parameter in the macro definition, the actual argument passed by the macro invocation is enclosed in quotation marks and treated as a string literal. The string literal then replaces each occurrence of a combination of the stringizing operator and formal parameter within the macro definition.

White space preceding the first token of the actual argument and following the last token of the actual argument is ignored. Any white space between the tokens in the actual argument is reduced to a single white space in the resulting string literal. Thus, if a comment occurs between two tokens in the actual argument, it is reduced to a single white space. The resulting string literal is automatically concatenated with any adjacent string literals from which it is separated only by white space
BTW, then you'll be fine with
#ifdef TEST_ECHO_OUTPUT
#define TEST_ECHO_MACRO(x) my_debug_out(x); // expands to call
#else
#define TEST_ECHO_MACRO(x) // expands to nothing
#endif
 
  TEST_ECHO_MACRO("test output")

Open in new window

>> note that I removed the ; at the end of the macro too
I've known some compilers to get ratty if you put a ; at the end of the usage of the macro , e.g

TEST_ECHO_MACRO(a); // In release this is a noop but leaves a rogue semi-colon behind

The release build is a no-op so you end up with a line that just contains a ; and whilst this shouldn't be an issue I've known some compilers to generate a warning. To get around this I normally use a do/while noop, like below. It'll get optimized away in release build but prevent the potential warning.
ifdef TEST_ECHO_OUTPUT
#define TEST_ECHO_MACRO(x) my_debug_out(x) // Will expect a semi-colon
#else
#define TEST_ECHO_MACRO(x) do {} while(false) // Will expect a semi-colon also, but will be optimized away
#endif

Open in new window

>> and would be required for

Not really since my_debug_out takes an int as parameter.


>> BTW, then you'll be fine with

Except for the fact that my_debug_out doesn't take a string as parameter, I still prefer not having a ; at the end of a macro ... It's so counter-intuitive this way :

        #define TEST_ECHO_MACRO(x) my_debug_out(x);

        int a = 0;
        TEST_ECHO_MACRO(a)                             /* <--- no ; at the end of this line */
        some_more_code();

that I prefer "forcing" the user of my macro to add the ;

        #define TEST_ECHO_MACRO(x) my_debug_out(x)

        int a = 0;
        TEST_ECHO_MACRO(a);                             /* <--- now the ; HAS to be there at the end of this line */
        some_more_code();
evilrix, good catch with not including the ";" in the macro leaving a rogue semicolon, isn't the solution then just to leave the semicolon in the macro?  Why do you need a loop in the no-test-macro?  
Infinity08, agree it's inconsistent to leave the semi-colon in that macro, but I think preferable to leaving stray semicolons around.  Make sense?
>>  isn't the solution then just to leave the semicolon in the macro
IMO, no. The way I suggest forces the semi-colon to be added at the end when you use it so it looks more naturally like a function call.

>> I prefer "forcing" the user of my macro to add the ;
Me too, hence my suggestion above :)
>>good catch with not including the ";" in the macro leaving a rogue semicolon

Well, I saw it, but a semicolon is not 'rogut' at all. It would expand to

void my_main_code()
{
  int a;
  a =1;

  ;

}

which is legal C/C++-.
>> which is legal C/C++-.
Agreed, I never said it wasn't... it's just that some (mainly older) compilers will issue a warning if you build with high warning levels. It also forces the caller to provide it so it makes the code more consistent (IMO). I'm not saying do it, I'm just pointing it out as a consideration.
>> I've known some compilers to generate a warning.

Ah, never seen that ... The C standard explicitly allows empty statements :

        (6.8.3) expression-statement:
                        expression(opt) ;

(the opt meaning optional of course)

What the C standard doesn't allow however, is a { } block followed by a ;
To avoid that, you DO need macro's like these if they involve { } blocks :

        #define SOME_BLOCK_MACRO do {                 \
                                                             int i = 0;       \
                                                             fun(i);           \
                                                          } while(0)
evilrx and Infinity08:

Interesting perspective on the need for consistency of semicolon use in main code, I see your point.  But should be no technical problems with leaving the semicolon in the macro, right?  Just requires extra care by the reviewer?

I would have trouble convincing people of the evilrx loop approach, and I don't want a stray semicolon, so that might be my best route.....
>> But should be no technical problems with leaving the semicolon in the macro, right?

Technically, it's ok, yes. I just think not having it in the macro is more consistent.


>> and I don't want a stray semicolon

Don't worry about a stray semicolon (empty statement) ... It's no problem at all. It's perfectly legal.

The loop approach evilrix showed was just to avoid warnings on certain old compilers.
jkr, your solution (including ; in macro) does *not* leave rogue semicolon, correct?  The way I saw it, evilrx's comment encouraged me back to your original suggestion.

I have seen warnings with rogue semicolons too, so I'm prejudiced against them.

By the way, really appreciate all the thoughts...
>> jkr, your solution (including ; in macro) does *not* leave rogue semicolon, correct?

Unless you actually use the macro like this :

        TEST_ECHO_MACRO(a);


>> I have seen warnings with rogue semicolons too, so I'm prejudiced against them.

Odd, I've never seen them for empty statements. Oh well ;)
Just one more thought about semi-colons and macros ti support I8 and my assertion. Consider the really contrived code below. See how the behavior changes if trace is on compared to when it's off? Enforcing consistent semi-colon termination for all functions and macros (and I do mean force and not rely on other programmers to follow a convention) will offer some protection against this kind of hard to track down bug.


#include <stdio.h>
 
#define DO_TRACE // Comment me to change behavior
 
#ifdef DO_TRACE
#define TRACE(x) printf("trace: %d\n", x);
#else
#define TRACE(x)
#endif
 
void foo()
{
	printf ("Part2\n");
}
 
int main()
{
 
	int n = 0;
	if (n < 10)
		printf ("Part1\n");
	else
		TRACE(n) // When DO_TRACE is disabled the semi-colon goes with it so foo() becomes part of this if/else
 
	foo();
 
	return 0;
}

Open in new window

>> >> I've known some compilers to generate a warning.

>> Ah, never seen that ... The C standard explicitly allows empty statements :

evil's right .. i'm currently working with a nintendo-ds compiler .. and we get warnings for those *lonesome* semicolons .. since we have set option "warning to errors" this became an issue ..

however, you're right jkr .. its legal anyway ..

ike

One last follow-on, then I'll close this out.  Given the defines

#ifdef TEST_ENABLE_OUTPUT
#define TEST_OUTPUT(x) print_test_message(x); // expands to call; note semicolon is in macro
#else
#define TEST_OUTPUT(x) // expands to nothing
#endif

and the calls

 int aa; // = 45;
  aa = 45;
        print_test_message("Hello1 %i,%i\r", aa, aa);
        TEST_OUTPUT("Hello2 %i,%i\r", aa, aa)

why would the outputs be:

?Hello1 45, 45
Hello2 7387, 2552

crazy stuff.  My function is below, just puts the characters out UART0.  Don't want to make this another draw on your time, just wanted to know any initial thoughts, then I'll start a new question if it gets involved...



char s_buf [100];

void print_test_message( flash char *format,  ...)
{

  va_list ap;
  va_start(ap,format);
  vsprintf(s_buf, format, ap);
  va_end(ap);

  putstr0(s_buf);
 
}  

Okay got some help from

http://en.wikipedia.org/wiki/Variadic_macro

This has some improvement, but still need to figure out that "?"

#ifdef TEST_ENABLE_OUTPUT
#define TEST_OUTPUT(...) print_test_message( __VA_ARGS__);
#else
#define TEST_OUTPUT(x) // expands to nothing
#endif
You're passing more parameters to the macro than it expects.
>> but still need to figure out that "?"

Can you show the complete code ?

Didn't I give you all the working parts?

Turns out the question mark follows the first call, so it seems like that's some left over crap in the buffer or something.  I'll need to talk to the guys who wrote that function...

Thanks a ton, guys, for all the help.
Are you using gcc? If not, variable arguments to a macro aren't possible, they aren't part of the standard.

This is the CodeVision compiler for Atmel microprocessors.  Apparently it accepts the variable-list macros, do you see any danger with using it?
>>Apparently it accepts the variable-list macros, do you see any danger with
>>using it?

If it works, that's fine, yet it won't be portable - that's the downside...
>> Turns out the question mark follows the first call, so it seems like that's some left over crap in the buffer or something.

That's why I was asking to see the complete code ;) To find where the question mark comes from.

Oh, I would never inflict all that on you.  That;s a 5,000 point question. Will throw that question to a coworker, at least I have the macro working so I'm happy.
Not sure if I am covering old ground here but there is a good reason for the dangling semi.

I think the code below should demonstrate.

Paul
#ifdef DO_TRACE
#define TRACE1(x) printf("trace: %d\n", x)
#else
#define TRACE1(x)
#endif
 
#ifdef DO_TRACE
#define TRACE2(x) printf("trace: %d\n", x);
#else
#define TRACE2(x) ;
#endif
 
// Check the code below with trace both on and off.
 
if ( a ) TRACE1(x)
else TRACE1(y)
 
if ( a ) TRACE1(x);
else TRACE1(y);
 
if ( a ) TRACE2(x)
else TRACE2(y)
 
if ( a ) TRACE2(x);
else TRACE2(y);

Open in new window

>> Not sure if I am covering old ground here but there is a good reason for the dangling semi
You mean, like this http:#20844363 ?
Forgive me ER. You already covered that one.

Paul
It's been a while, Paul. What have you been up to ?
It has indeed. :)

Not sure I'm back yet but I do have a browse in 'C' most days. You guys hold the fort so well :)

I've been working on a private project but that's coming to an end soon. My next is a technical one in C so I hope to be visiting often.

Paul
Great to hear from you. The C zone is not the same without you ;)