4

We have a profiling framework which can be enabled and disabled at compile time.

All the various calls to the framework are done through macros, eg:

PROFILE_START(msg)
PROFILE_END(msg)

The macros then resolve to the actual profiler call when profiling is enabled, and to nothing when disabled

#ifdef PROFILING_ENABLED
#    define PROFILE_START(msg) currentProfiler().start(msg)
#    define PROFILE_END(msg)   currentProfiler().end(msg)
#else
#    define PROFILE_START(msg)
#    define PROFILE_END(msg)   
#endif

We have various different components in our framework, and I want to enable profiling in each component.

I'd like to be able to selectively enable profiling in each component.

My idea is to prefix all the profiler macros with the component's name, eg:

FOO_PROFILE_START(msg)
FOO_PROFILE_END(msg)

BAR_PROFILE_START(msg)
BAR_PROFILE_END(msg)

I could manually create

#ifdef ENABLE_FOO_PROFILING
#    define FOO_PROFILE_START(msg) PROFILE_START(msg)
#    define FOO_PROFILE_END(msg) PROFILE_END(msg)
#else
#    define FOO_PROFILE_START(msg) 
#    define FOO_PROFILE_END(msg) 
#endif

#ifdef ENABLE_BAR_PROFILING
#    define BAR_PROFILE_START(msg) PROFILE_START(msg)
#    define BAR_PROFILE_END(msg) PROFILE_END(msg)
#else
#    define BAR_PROFILE_START(msg) 
#    define BAR_PROFILE_END(msg) 
#endif

However, this is both tedious and error-prone.

Any time a new feature is added to the profiling framework I would have to find all my component specific macros and add a new macro to each of them.

What I'm looking for is a way to automatically generate the component-prefixed macros.

#ifdef ENABLE_FOO_PROFILING
    ADD_PREFIX_TO_ENABLED_PROFILING_MACROS(FOO)
#else
    ADD_PREFIX_TO_DISABLED_PROFILING_MACROS(FOO)
#endif

The net result of the above would be to create all the FOO_PROFILE_XXX macros I would have done manually.

Questions:

  • Is such a helper macro possible?
  • Is there a better way of achieving what I'm looking for?

I'm happy to use BOOST_PP if necessary.


Before posting this question I tried figuring this out myself, and the code I came up with follows, which may serve to show the road I was going down

#include <stdio.h>

#define PROFILE_START(msg) printf("start(%s)\n", msg);
#define PROFILE_END(msg)   printf("end(%s)\n", msg);

#define ENABLE(prefix) \
    #define prefix ## _PROFILE_START PROFILE_START \
    #define prefix ## _PROFILE_END   PROFILE_END

#define DISABLE(prefix) \
    #define prefix ## _PROFILE_START \
    #define prefix ## _PROFILE_END

#define ENABLE_FOO

#ifdef ENABLE_FOO
    ENABLE(FOO)
#else
    DISABLE(FOO)
#endif

#ifdef ENABLE_BAR
    ENABLE(BAR)
#else
    DISABLE(BAR)
#endif


int main()
{
    FOO_PROFILE_START("foo");
    FOO_PROFILE_END("foo");

    BAR_PROFILE_START("bar");
    BAR_PROFILE_END("bar");

    return 0;
}
too honest for this site
  • 11,417
  • 3
  • 27
  • 49
Steve Lorimer
  • 22,912
  • 14
  • 99
  • 180
  • 1
    Don't spam tags! This is clearly not C. – too honest for this site May 25 '17 at 19:29
  • 5
    @Olaf it's regarding the C-preprocessor, so anything which works in C would work here. Apolgogies if you feel it's spamming, that was not my intention – Steve Lorimer May 25 '17 at 19:29
  • 1
    Your code is C++, not C. And there might be better ways to achive whatever you want usign C++ features directly. It obviously is not possible to define macros **in a macro**. But we are not a consulting site. – too honest for this site May 25 '17 at 19:31
  • 3
    @Olaf it is not obvious to me that it is not possible to create macros in a macro, I'm not sure what you're referring to? I also am not sure what you mean when you say we aren't a consulting site – Steve Lorimer May 25 '17 at 19:32
  • 2
    @Olaf updated the code so it is C, to better reflect the question I have – Steve Lorimer May 25 '17 at 19:35
  • 1
    Can you describe in more detail (e.g. sample code, desired prepro output) the problem you want to solve? Avoid describing it in terms of the solution you imagine. https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem A preprocessor statement is always one line (apart from continuation) and starts with a `#`. That is why no macro is possible which defines other macros. However, prepro magic exists, you just need to leave more room by not insisting on a certain (impossible) way. – Yunnosch May 25 '17 at 19:47
  • @Yunnosch I've updated the question, I hope it's better now – Steve Lorimer May 25 '17 at 19:56
  • 1
    @SteveLorimer, whether it's obvious to you or not, you cannot define macros via other macros. A macro can expand to text that has the *form* of a preprocessing directive, such as a macro definition, but the preprocessor will never recognize the resulting text as a directive. This follows from [paragraph 6.10/2](http://port70.net/~nsz/c/c11/n1570.html#6.10p2) of the standard. – John Bollinger May 25 '17 at 19:57
  • @JohnBollinger thanks for the comment, understood. My previous comment was more to the other user's statement "it is *obviously* not possible". – Steve Lorimer May 25 '17 at 19:59
  • 3
    Look at the discussion in [C `#define` macro for debug printing](https://stackoverflow.com/questions/1644868/c-define-macro-for-debug-printing/1644898#1644898). Although that's about debug code, it is readily adapted to profiling code, or any other special case. Personally, I'd probably go with a variant on `#define PROFILE_START(component, msg) …` where the component can be used in conditions etc. Perhaps `#define PROFILE_START(component, msg) (profile_ ## component ? currentProfiler().start(msg) : (void)0)` or similar. That assumes you have `profile_BAR` etc defined (as macros?). – Jonathan Leffler May 25 '17 at 20:00
  • I've frequently wanted something like this; namespaces for macros or for C are strongly desired. I think the best you can do is write a tool which generates the macros for you. – Justin May 25 '17 at 20:01
  • I think it's not possible because you're trying to define something at run time and that's not possible because the preprocessor replace macros with their value before you are even at compiling time. There's definitly no way you can change a macro at run time. – simo-r May 25 '17 at 20:02
  • It is still not C; please stopp spamming tags. – too honest for this site May 25 '17 at 21:41

3 Answers3

2
Is such a helper macro possible?

No. With the exception of pragmas, you cannot execute a preprocessing directive in a macro.

You can do something very similar using pattern matching. By taking the varying parts out of the macro name, and putting it inside the macro itself, you can make a form that allows enabling/disabling for arbitrary names.

This requires a tiny bit of preprocessor metaprogramming (which is a constant overhead; i.e., doesn't vary as you add modules), so bear with me.

Part 1: A C preprocessor solution

Using this set of macros:

#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(_,X,...) X
#define SWITCH(PREFIX_,PATTERN_,DEFAULT_) SECOND(GLUE(PREFIX_,PATTERN_),DEFAULT_)
#define EAT(...)

#define PROFILER_UTILITY(MODULE_) SWITCH(ENABLE_PROFILER_FOR_,MODULE_,DISABLED)
#define PROFILER_IS_DISABLED ,EAT
#define PROFILE_START_FOR(MODULE_, msg) SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START)(msg)
#define PROFILE_END_FOR(MODULE_, msg)   SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_END)(msg)

...which you can include in each module, you will gain the ability to do this:

PROFILE_START_FOR(FOO,msg)
PROFILE_END_FOR(FOO,msg)
PROFILE_START_FOR(BAR,msg)
PROFILE_END_FOR(BAR,msg)
PROFILE_START_FOR(BAZ,msg)
PROFILE_END_FOR(BAZ,msg)

All of these macros, by default, expand to nothing; you can change this by defining ENABLE_PROFILER_FOR_xxx for any subset of FOO, BAR, or BAZ to expand to , (or ,ON if that looks better), in which case the corresponding macros will expand (initially, before your own macros come in) to PROFILE_START(msg)/PROFILE_END(msg); and the rest will continue expanding to nothing.

Using the FOO module as an example, you can do this with a "control file": #define ENABLE_PROFILER_FOR_FOO ,ON; the command line: ... -DENABLE_PROFILER_FOR_FOO=,ON; or in a makefile; CFLAGS += -DENABLE_PROFILER_FOR_FOO=,ON.

Part 2a: how it works; the SWITCH macro

#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(_,X,...) X
#define SWITCH(PREFIX_,PATTERN_,DEFAULT_) SECOND(GLUE(PREFIX_,PATTERN_),DEFAULT_)

GLUE here is your typical indirect paste macro (allowing arguments to expand). SECOND is an indirect variadic macro returning the second argument.

SWITCH is the pattern matcher. The first two arguments are pasted together, comprising the pattern. By default, this pattern is discarded; but due to the indirection, if that pattern is an object like macro, and that pattern's expansion contains a comma, it will shift a new second argument in. For example:

#define ORDINAL(N_) GLUE(N_, SWITCH(ORDINAL_SUFFIX_,N_,th))
#define ORDINAL_SUFFIX_1 ,st
#define ORDINAL_SUFFIX_2 ,nd
#define ORDINAL_SUFFIX_3 ,rd
ORDINAL(1) ORDINAL(2) ORDINAL(3) ORDINAL(4) ORDINAL(5) ORDINAL(6)

...will expand to:

1st 2nd 3rd 4th 5th 6th

In this manner, the SWITCH macro behaves analogous to a switch statement; whose "cases" are object-like macros with matching prefixes, and which has a default value.

Note that pattern matching in the preprocessor works with shifting arguments, hence the comma (the main trick being that of discarding unmatched tokens by ignoring an argument, and applying matched tokens by shifting a desired replacement in). Also for the most general case with this SWITCH macro, you need at a minimum to ensure that all PREFIX_/PATTERN_ arguments are pasteable (even if that token isn't seen, it has to be a valid token).

Part 2b: combined switches for safety

A lone switch works like a case statement, allowing you to shove anything in; but when the situation calls for a binary choice (like "enable" or "disable"), it helps to nest one SWITCH in another. That makes the pattern matching a bit less fragile.

In this case, the implementation:

#define PROFILER_UTILITY(MODULE_) SWITCH(ENABLE_PROFILER_FOR_,MODULE_,DISABLED)
#define PROFILER_IS_DISABLED ,EAT
#define PROFILE_START_FOR(MODULE_, msg) SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START)(msg)
#define PROFILE_END_FOR(MODULE_, msg)   SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_END)(msg)

...uses PROFILER_UTILITY as the inner switch. By default, this expands to DISABLED. That makes the pattern in SWITCH(PROFILER_IS_,PROFILER_UTILITY(MODULE_),PROFILE_START) by default be PROFILER_IS_DISABLED, which shoves in EAT. In the non-default case of PROFILER_UTILITY, the outer switch kicks in making it expand to PROFILE_START. PROFILE_END_FOR works analogously.

The EAT macro takes (msg) in both cases to nothing; otherwise, the original macro's called.

Is there a better way of achieving what I'm looking for?

Depends on what you're looking for. This approach shows what's possible with the C preprocessor.

H Walters
  • 2,346
  • 1
  • 8
  • 12
1

I personally would go for something like

#include <stdio.h>

#define FOO_ENABLED 1
#define BAR_ENABLED 0

#define PROFILE_START(FLAG, msg) \
   { if (FLAG) printf("start(%s)\n", msg); }

int main()
{
    PROFILE_START(FOO_ENABLED, "foo")
    PROFILE_START(BAR_ENABLED, "bar")
    return 0;
}

Any decent compiler would not generate any instructions for the if statement anyway.

user1620443
  • 764
  • 3
  • 14
0
  • Is such a helper macro possible?

No. As was covered in comments, you cannot generate macro definitions via macros.*

  • Is there a better way of achieving what I'm looking for?

Since the macro idea won't work,* any alternative that does work is better. Basically, you're looking for a code generator -- a program that will take as input a list of modules and produce as output C source (maybe a header) containing definitions of all the profiling macros for all the modules. You could write such a program in pretty much any language -- C, python, perl, shell script, whatever. Depending on your technology preferences and project context, you might even go with something like XSLT.

Each source file that wants to get the profiling macros then just #includes the generated header.


*In fact, you could use the C preprocessor, by performing a separate, standalone run on a different, for-purpose input file. But you cannot generate the macros in-place when you compile the source file(s) that wants to use them.

John Bollinger
  • 121,924
  • 8
  • 64
  • 118