Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C preprocessor macro specialisation based on an argument

Is it possible to have one macro expanded differently for one specific argument value and differently for all other arguments?

Say I define a current user:

#define CURRENT_USER john_smith

What I want to be able to do is to have a macro that will be expanded differently if user passed matches CURRENT_USER. Mind you that I don't know all possible user a priori. The most basic case:

#define IS_CURRENT_USER(user)                   \
    /* this is not valid preprocessor macro */  \
    #if user == CURRENT_USER                    \
        1                                       \
    #else                                       \
        0                                       \
    #endif                                      

With macro like that every other macro relying on the username could be done following way:

#define SOME_USER_SPECIFIC_MACRO(user) SOME_USER_SPECIFIC_MACRO_SWITCH_1(IS_CURRENT_USER(user))

#define SOME_USER_SPECIFIC_MACRO_SWITCH_1(switch)   SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch) // expand switch ...
#define SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch)   SOME_USER_SPECIFIC_MACRO_##switch         // ... and select specific case

#define SOME_USER_SPECIFIC_MACRO_0  ... // not current user
#define SOME_USER_SPECIFIC_MACRO_1  ... // current user

Is this possible?

EDIT: Let me clarify. Say each programmer defines different CURRENT_USER in their config header. I want user specific macros to exand to something meaningful if and only if their user argument matches CURRENT_USER. As I would like those macros to contain _pragmas it can't be runtime check (as proposed in some anwsers below).

EDIT: Again, clarification. Say there's macro to disable optimisation of some sections of code:

#define TURN_OPTIMISATION_OFF __pragma optimize("", off)

Some programmers want to turn optimisation off for different sections of code but not all at one time. What I'd like is to have a macro:

#define TURN_OPTIMISATION_OFF(user) /* magic */

That will match user argument against CURRENT_USER macro, taken from per-programmer config file. If the user matches macro is expanded into pragma. If not, to nothing.

like image 224
gwiazdorrr Avatar asked Jul 24 '12 13:07

gwiazdorrr


People also ask

What is macro with arguments in C?

Function-like macros can take arguments, just like true functions. To define a macro that uses arguments, you insert parameters between the pair of parentheses in the macro definition that make the macro function-like. The parameters must be valid C identifiers, separated by commas and optionally whitespace.

What are conditional preprocessing macro?

In this tutorial, you will be introduced to c preprocessors, and you will learn to use #include, #define and conditional compilation with the help of examples. Working of C Preprocessor. The C preprocessor is a macro preprocessor (allows you to define macros) that transforms your program before it is compiled.

What is difference between preprocessor and macros?

Macro: a word defined by the #define preprocessor directive that evaluates to some other expression. Preprocessor directive: a special #-keyword, recognized by the preprocessor. Show activity on this post. preprocessor modifies the source file before handing it over the compiler.

What is macro processing in preprocessor?

The C preprocessor is a macro processor that is used automatically by the C compiler to transform your program before actual compilation. It is called a macro processor because it allows you to define macros, which are brief abbreviations for longer constructs.


5 Answers

Well first, you can do pattern matching with the preprocessor using the ##. This is how an IIF macro could be defined:

#define IIF(cond) IIF_ ## cond
#define IIF_0(t, f) f
#define IIF_1(t, f) t

However there is one problem with this approach. A subtle side effect of the ## operator is that it inhibits expansion. Heres an example:

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// This will however expand to IIF_A()(true, false)
// This is because A() doesn't expand to 1,
// because its inhibited by the ## operator
IIF(A())(true, false) 

The way to work around this is to use another indirection. Since this is commonly done, we can write a macro called CAT that will concatenate without inhibition.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

So now we can write the IIF macro:

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// And this will also now correctly expand to true
IIF(A())(true, false)

With pattern matching we can define other operations, such as COMPL which takes the complement:

// A complement operator
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
// An and operator
#define BITAND(x) PRIMITIVE_CAT(BITAND_, x)
#define BITAND_0(y) 0
#define BITAND_1(y) y

Next, detection techniques can be used to detect if the parameter is a certain value or if it is parenthesis. It relies on variadic arguments expanding to different number of parameters. At the core of detection is a CHECK macro with a PROBE macro like this:

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

This is very simple. When the probe is given to the CHECK macro like this:

CHECK(PROBE(~)) // Expands to 1

But if we give it a single token:

CHECK(xxx) // Expands to 0

So with this, we can create some detection macros. For instance, if we want to detect for parenthesis:

#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)
IS_PAREN(()) // Expands to 1
IS_PAREN(xxx) // Expands to 0

Next, we need to do a comparison of two tokens, we can rely on the fact that macros don't expand recursively. We force the macro to expand recursively inside of the other macro. If the two tokens are the same then the it will be expanding the macros recursively, which we will detect by trying detect if they expanded to parenthesis or not, here is the COMPARE macro:

#define COMPARE(a, b) PRIMITIVE_COMPARE(a, b)
#define PRIMITIVE_COMPARE(a, b) \
    IIF( \
        BITAND \
            (IS_PAREN(COMPARE_ ## a(()))) \
            (IS_PAREN(COMPARE_ ## b(()))) \
    )( \
        COMPL(IS_PAREN( \
            COMPARE_ ## a( \
                COMPARE_ ## b \
            )(()) \
        )), \
        0 \
    ) \

Each token you want to compare you would define like this:

// So you would define one for each user
#define COMPARE_john_smith(x) x
#define COMPARE_another_user_name(x) x

Now, I don't fully understand the final output you want generated, so say you have a macro for generating code for the current user and one for other users:

#define MACRO_CURRENT_USER(user) ...
#define MACRO_OTHER_USER(user) ...

Then you can write something like this:

// Detects if its the current user
#define IS_CURRENT_USER(user) COMPARE(user, CURRENT_USER)
// Your macro
#define MACRO(user) IIF(IS_CURRENT_USER(user))(MACRO_CURRENT_USER, MACRO_OTHER_USER)(user)
like image 137
Paul Fultz II Avatar answered Oct 31 '22 22:10

Paul Fultz II


Tuns out it is possible. This anwser is based on Pauls macros, but much simpler and does not need definition for each user.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define PROBE(x) x, 1 

Now, because of the MSVC bug I had to modify CHECK macro a bit.

#define MSVC_VA_ARGS_WORKAROUND(define, args) define args
#define CHECK(...) MSVC_VA_ARGS_WORKAROUND(CHECK_N, (__VA_ARGS__, 0))
#define CHECK_N(x, n, ...) n

Instead of defining CURRENT_USER I switched to following macros.

#define ENABLE_USER_gwiazdorrr () // gwiazdorrr is now enabled
#define ENABLE_USER_foo ()        // foo is also enabled
// #define ENABLE_USER_bar ()     // bar is NOT enabled

It actually gives more flexibility, because one can enable multiple user at the same time. The parenthesis is required. The macros below actually detect, whether ENABLE_USER_<user> is expanded into parenthesis or not.

#define USER_ENABLED_PROBE(user)            USER_ENABLED_PROBE_PROXY( ENABLE_USER_##user ) // concatenate prefix with user name
#define USER_ENABLED_PROBE_PROXY(...)       USER_ENABLED_PROBE_PRIMIVIE(__VA_ARGS__)       // expand arguments
#define USER_ENABLED_PROBE_PRIMIVIE(x)      USER_ENABLED_PROBE_COMBINE_##x                 // merge
#define USER_ENABLED_PROBE_COMBINE_(...)    PROBE(~)                                       // if merge successful, expand to probe

USER_ENABLED_PROBE(gwiazdorrr) // expands to ~, 1
USER_ENABLED_PROBE(bar)        // expands to USER_ENABLED_PROBE_COMBINE_bar

From now it is a childs play:

#define IS_USER_ENABLED(user) CHECK(USER_ENABLED_PROBE(user))

IS_USER_ENABLED(gwiazdorrr)   // expands to 1
IS_USER_ENABLED(bar)          // expands to 0

Having this macro and IIF (thanks Paul!) I decided to implement the optimisation macro mentioned in the original question:

#define TURN_OPTIMISATION_OFF(user) IIF( IS_USER_ENABLED(user) ) \
    (\
        __pragma optimize("", off),\
        /* nothing */ \
    )

TURN_OPTIMISATION_OFF(gwiazdorrr) // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(foo)        // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(bar)        // nothing emitted

Thanks for input!

EDIT: here's the GCC version: http://ideone.com/129eo

like image 30
gwiazdorrr Avatar answered Oct 31 '22 21:10

gwiazdorrr


Preprocessing takes place before compilation.

If user is known to the preprocessor, then yes:

#define user 4
#define CURRENT_USER 4
#define IS_CURRENT_USER 1

#if user == CURRENT_USER
#define IS_CURRENT_USER(user) 1
#else
#define IS_CURRENT_USER(user) 0
#endif

But this is utterly useless and I doubt it's what you actually have.

Otherwise, no. Don't abuse mecros and the preprocessor.

After your edit:

No, what you want is definitely not possible (turn off optimizations depending on user).

like image 44
Luchian Grigore Avatar answered Oct 31 '22 21:10

Luchian Grigore


The code below is not sensitive to the MSVC bug. The ... arguments are not separated.

#define IF_USER_ENABLED(x,...) IF_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) 
#define IF_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,GT4,3,,__VA_ARGS__)
#define ARGS_ARG2(x,y,z,...) ARGS_ ## z (x,y,z,__VA_ARGS__)
#define ARGS_3(x,y,z,w,...) w
#define ARGS_GT4(x,y,z,w,v,...) __VA_ARGS__

#define IF_USER_DISABLED(x,...) IF_NOT_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) 
#define IF_NOT_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,4,GT3,,__VA_ARGS__)
#define ARGS_4(x,y,z,w,v,...) v
#define ARGS_GT3(x,y,z,w,...) __VA_ARGS__

#define ENABLE_USER_foo ,
//#define ENABLE_USER_bar ,
like image 36
brac37 Avatar answered Oct 31 '22 22:10

brac37


If the argument to the macro is always very constant (even literally and lexically) you could play tricks with concatenation, something like

#define SOME_MACRO(T) SOME_MACRO_FOR_##T
#define SOME_MACRO_FOR_0 somethinghere()
#define SOME_MACRO_FOR_1 somethingelse()

Otherwise, you could have

#define CURRENT_USER ((user == THE_USER)?(something()):(somethingelse()))

Or use a static inline tiny function:

static inline int current_user(int user)
{
   return (user==THE_USER)?(something()):(somethingelse());
}

(Notice that if user is a constant, perhaps after some previous compiler optimization, the compiler would optimize that to something simpler, and the compiled binary won't test user at runtime. See also __builtin_constant_p if compiling with gcc).

But I believe your preprocessor tricks might make your code less readable. Think twice when making them.

And you didn't tell us what is your exact macro usage. Do you use it as an lvalue?

As you say, the preprocessor cannot expand to preprocessing directive, so your example :

#define IS_CURRENT_USER(user)                   \
  /* this is not valid preprocessor macro */  \
  #if user == CURRENT_USER                    \
      1                                       \
  #else                                       \
      0                                       \
  #endif       

is (as you say) incorrect.

You are only allowed to do things like :

 #if user == CURRENT_USER
 #define IS_CURRENT_USER(U) 1
 #else
 #define IS_CURRENT_USER(u) 0
 #endif

I knowingly am using u not user as the formal argument to your macro IS_CURRENT_USER for readability (that formal is not expanded, only its occurrences in the macro are).

Do you realize that preprocessing happens "before" compilation? Did you run e.g. gcc -C -E to get the preprocessed ouput? It should be instructive!

Read more about the C preprocessor

BTW, did you consider generating some C code (perhaps to be #include-d somewhere) with a script (or your own generator, or autotools, or a generic preprocessor like autogen or m4)? You could generate (from e.g. a user base such as /etc/passwd on Linux, or NIS/YP, LDAP or with getpwent(3) ...) an #include-d myoptim.h with things like

#if CURRENT_USER_ID==1234
#define OPTIMIZATION_FOR_PAUL _pragma(GCC(optimize,"-O1"))
#else
#define OPTIMIZATION_FOR_PAUL /*nothing*/
#endif
#if CURRENT_USER_ID==3456
#define OPTIMIZATION_FOR_ALICE _pragma(GCC(optimize,"-O1"))
#else
#define OPTIMIZATION_FOR_ALICE /*nothing*/
#endif

and ask Paul (assuming his uid is 1234) to prefix his functions with OPTIMIZATION_FOR_PAUL and put CFLAGS=-DCURRENT_USER_ID=$(shell id -u) in your Makefile; I find that ugly (and it does not address the fact that optimization might alter globally behavior of ill-coded programs).

You could customize GCC with e.g. a MELT extension providing a custom pragma or builtin for your needs, but I find that weird in your particular case.

NB. Historically, cpp was designed to be a quick textual processor, not Turing-complete. In the old days (1980-s Unix) it ran as a separate process, with the real compilation done by cc1, and the cc compiler was just a shell script driving them (with as and ld). Today, gcc is a small driver program, but cc1 incorporates the pre-processor for performance reasons. Still, the C standard is specified so that the pre-processing can be a separate program from the compiler proper.

like image 38
Basile Starynkevitch Avatar answered Oct 31 '22 21:10

Basile Starynkevitch