Reputation: 13551
For a long time now, we've had a logging system that worked much like this:
#define LOG( LEVEL, FORMAT, ... ) my_log_function( LEVEL, __FUNCTION__, \
__LINE__, FORMAT, __VA_ARGS__ )
my_log_function
will check the level of logging currently in place, and if acceptable, will do some pretty printing (file/line where it was logged, time etc..) of the informations passed.
Now, problem is, this macro definition has two massive drawback:
1/ When used, the arguments passed in that macro are evaluated, which means lots of performance hits when your code is crowded by LOG() calls.
See example here:
LOG( INFO, "The parameters: %s %d %d\n", heavyMethod().name(),
heavyMethod2().id(), work_done_in_this_function());
Even if the "INFO" logging level is deactivated, all the parameters will be evaluated before entering the function.
If you log the function calls, you'll see this happening:
heavyMethod()
name()
heavyMethod2()
id()
work_done_in_this_function()
my_log_function()
That's pretty bad when you have 1000's of LOG() calls.
Solution is simple: take out of my_log_function
the code that checks the level and modify the LOG() definition like this:
#define LOG( LVL, FMT, ... ) do{ if( level_enabled(LVL) ) \
{ \
my_log_function( LVL, ...); \
} \
}while(0)
This makes sure that when the log level is not sufficient, the parameters are not evaluated (since they are in a bracket block).
2/ As you've seen in my example, the last function that is called is doing some sort of work that wouldn't be done if the LOG() function wasn't called.
This happens quite a lot in our code (and I know, this SUCKS HARD, people have lost fingers to this already).
With the enhancement I made in point 1/, we now have to check every LOG() call to see if some work was done in there that isn't done anymore, now that we neutralized the calls.
This is where you guys enter: do you know a simple method that would check if an argument to a function is actually modifying something?
The project is in C++, and most functions that "don't modify anything" are marked as const.
Note that this includes some tricky things like: LOG( INFO, "Number of items: %d\n", _item_number++);
, where _item_number
is an object's class member (thus doesn't get incremented if INFO level is not activated :-( ).
TL;DR: NEVER, NEVER do work in printf(). ALWAYS do it beforehand:
// BAD
printf("Number: %d\n",++i);
// GOOD
i++;
printf("Number: %d\n", i);
Upvotes: 2
Views: 1253
Reputation: 9642
Basically, you're trying to check for pure functions (i.e. ones which have no side effects). An easy way to check this is to check that it does not make any writes to global memory.
GCC supports a couple of ways of enforcing this. First, there is the const
attribute, which when applied to a function, causes it not to be able to read or write from global memory (only its own stack). The issue is, this may be a little restrictive for what you want.
There's also the pure
attribute, which is basically the same, but allows for access from global memory, but not to it, which should also enforce what you want.
Each of these are applied by putting __attribute__(pure)
(or __attribute__(const)
) by the method declaration. Sadly, I don't think there's a way of enforcing this per call, although there may be a way using function pointers. I'll update if I find one.
Ref: http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
EDIT: As listed here, it may be possible to apply this to a function pointer although I don't think there's a way to do it without embedding a function pointer into each method call in your macro (which needs to know the argument layout of each function, anyway) or declaring all your functions as pure.
EDIT2: Yeah, this also won't catch things like i++
in your parameter lists. Those will have to be done using some regex magic I think.
Upvotes: 2