tags:

views:

241

answers:

5

Hi everyone,

I was playing with a macro to enable/disable traces when I came out with the following code when the macro is disabled:

int main()
{

  ("Hello world");

}

This code is valid and I got the desired effect (nothing happens when the macro is disabled) but I couldn't figure out what exactly is happening. Is the compiler seeing the parenthesis as a "nameless" method declaration?

To make it clearer the code is :

 #ifdef TRACE

    #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
 else

    #define trace
 #endif

int main()
{

  trace("Hello world");

}

Thanks in advance.

+17  A: 

If the function name is missing, as in your first example, then it is not a "parenthesis operator". It is simply a syntactic element of an expression that alters the association between operators and operands. In this case it simply does nothing. What you have is just an expression

"Hello world";

which evaluates to a value of char * type, and that value is ignored. You can surround that expression in a redundant pair of ()

("Hello world");

which will not change anything.

In exactly the same way you can write

(5 + 3);

in the middle of your code and get an expression that evaluates to value 8, which is immediately discarded.

Usually compilers generate no code for expression statements that have no side effects. In fact, in C language the result of every expression statement is discarded, so the only expression statements that "make sense" are expression statements with side effects. Compilers are normally fairly good at detecting effectless statements and discarding them (sometimes with a warning).

The warning could be annoying, so writing effectless expression statements like

"Hello world";

might not be a good idea. Typically compilers recognize a cast to void as a request not to generate this warning

(void) "Hello world";

So you might consider redefining your macro accordingly.

Of course, using the above trace technique, you have to remember that if you put something that does have a side effect as an argument for your macro

trace("%d\n", i++);

then in "disabled" form it will look as follows

("%d\n", i++);

(two subexpressions, chained by a comma operator into one expression). The side effect of incrementing i persists in this case, it does not get disabled. The whole thing is equivalent to plain

i++;

Also if you use a function call as an argument

trace(get_trace_name());

the "disabled" form will look as

(get_trace_name());

and the compiler might not be smart enough to realize that the call to get_trace_name() should be discarded. So, be careful when using your macro. Avoid arguments with side effects, avoid arguments with function calls, unless, of course, it is your intent to preserve the side effects when disabling the actual tracing.

AndreyT
The other thing is that it is important to avoid side-effects in the arguments to `trace`, but that's a good idea anyway.
Donal Fellows
@Donal Fellows: Thanks. I was just writing an extra bit about it :)
AndreyT
That is a thorough explanation, thanks for spending the time writing it :)
Andre
+2  A: 
("Hello world");

is an expression returning a constant pointer to a string. This value is not consumed.

Parenthesis have no specific role and you can omit them:

"Hello world";
mouviciel
But what happens in case I have a more elaborated printf? For example if instead of "Hello World" I had:<br/>int main <br/>{ int x; char * str = "Hello World"; trace("Test, %d, %s", x, str);}the trace would be replaced with nothing (trace disabled case) leaving the following expression:("Test, %d, %s", x, str);which also compiles and do nothing.
Andre
A sequence of expressions separated by `,` is correct in C. The value of the resulting expression is the value of the last expression of the sequence.
mouviciel
So everything will be evaluated but the "str" will be the value returned? So the side effect for this approach is that code is being evaluated without need.
Andre
It may even be dangerous if you want to trace something like: `trace("%d",i++);`
mouviciel
Very good. Point. Thanks a lot for your the help and all the others to.
Andre
A: 
#ifdef TRACE
   #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
#else
   #define trace
#endif

int main {
  trace("Hello world");
}

The way that macros work in C is that the compiler will (essentially)* do a literal replace of the identifier.

So in your case, there are two options depending on the value of the #IFDEF

trace("Hello world");

can become

  1. printf("%s %d -> ",__FILE__, __LINE__);printf("Hello world");

or

  1. ("Hello world");

The first option is a sequence of valid C code which consists of two printf statements. The second option is a sequence of valid C code which consists of a string (char *) inside unnecessary braces.

+3  A: 

Whether it works or not may depend on exactly what you pass as the arguments to the macro (see the side-effects issue mentioned by AndreyT). In this case it is benign. However the following is probably safer since it will result in no text being inserted when the macro is processed and TRACE is not defined:

#ifdef TRACE
    #define trace printf("%s %d -> ",__FILE__, __LINE__);printf
#else
    #define trace( ... )
#endif

assuming your compiler supports variadic macros. If it does the following would be a better definition perhaps:

#ifdef TRACE
    #define trace( fmt, ...) printf("%s %d -> " fmt, __FILE__, __LINE__, __VA_ARGS__ ) ;
#else
    #define trace( ... )
#endif

Note that the lack of a comma between "%s %d -> " and fmt is deliberate and required. Note also that the fmt argument must be a literal string constant in order for adjacent string literal concatenation to occur - a variable of any kind would generate an error, but it is bad practice to use a variable for a format specifier in any case.

Clifford
You get into trouble with strict C99 there. Apparently, you'd have to put a comma after the string when calling it in that case. IMO this is somewhere where the C99 spec is wrong.
Donal Fellows
@Donal Fellows: Are you sure? The constraint in this case is that `fmt` *must* be a string literal in order to take advantage of adjacent string literal concatenation; I should have been clear on that point. It is good practice in any case to only use string literal constants as format specifiers. It allows code such as `trace( "var = %d", var ) ;` to output `main.c 10 -> var = 24` for example. Maybe I misunderstand your point about C99; can you cite the ISO reference for this assertion?
Clifford
Update, from the working draft N1905=05-0165 (because its free) *2.3.4 String Literals, paragraph 3 "In translation phase 6(2.1),adjacent string literals are concatenated"*. I realise the working draft is not definitive, but it seems implausible somehow that this long standing aspect of C and C++ would change.
Clifford
The issue is with the "calling" of the macro. The problem (according to a page I googled up) is that wherever there are commas in the defined argument list, they have to be matched in the actual parameters. Which means that `trace("%d",10)` works but `trace("foobar")` is illegal; `trace("foobar",)` would be OK, but that's lacking in the Tao of C. (Well, IMO anyway).
Donal Fellows
@Donal Fellows: Ah, I see; good point. I think I could live with `trace("%s", "foobar")` if I had to, given the utility the macro otherwise provides.
Clifford
A: 

If your compiler supports C99 (or you're using gcc which had this feature earlier) you can use variadic macros:

#ifdef TRACE
#define trace(...) printf("%s %d -> ",__FILE__, __LINE__);printf(__VA_ARGS__)
#else
#define trace(...)
#endif

This avoids the problems you can get with side-effects in arguments. If you have a strict C89 compiler, you've got to just avoid side-effects...

Donal Fellows
VC++ 2005 and later also support variadic macros despite not supporting C99. You also do not need the second printf() if you concatenate the format string (see my earlier post); this makes the macro usable *anywhere* a function call is valid.
Clifford