0

Is there a way to define a macro checking if another macro is defined and non-zero or respectively not defined or defined as zero?

I recently ran into the problem that different people define their macros in a different way if they are used as flags. While compiling some define their macro without a value (eg. -DDEBUG) and some set it to non-zero (eg. -DDEBUG=1).

I actually found code similar to it: (defined(macro ## _SET) && (macro ## _SET)). The solution for this to work is the concatenation ## which prevents the macro expansion. Is there a way around it?

Environment

GNU Arm Embedded Toolchain (arm-none-eabi-*) with GNU ISO C99 (std=gnu99).

krnd
  • 1
  • 2
  • `#if defined(foo) && (foo > 0)` – Lundin Dec 13 '17 at 12:20
  • @Lundin But can you wrap that into a macro? E.g. `#if ENABLED(foo)`? I failed. :-) – unwind Dec 13 '17 at 12:28
  • Note that POSIX [`c99`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html) stipulates: _`-D name[=value]` Define name as if by a C-language `#define` directive. If no `=value` is given, a value of 1 shall be used._ – Jonathan Leffler Dec 13 '17 at 12:30
  • @Lundin: should that `>` be `!=`? – Jonathan Leffler Dec 13 '17 at 12:31
  • You said "is defined _or_ non-zero"; did you mean "is defined _and_ non-zero"? If it is non-zero, it is defined; only if it is undefined can it be treated as zero. It could be defined as zero, but that doesn't matter with 'or', but obviously it does with 'and'. (In a preprocessor condition, if there are any unexpanded identifiers left after macro expansion, they're treated as zeroes.) – Jonathan Leffler Dec 13 '17 at 12:36
  • @JonathanLeffler Ehm ok - It's *defined and non-zero* or *not defined or zero*. – krnd Dec 13 '17 at 12:43
  • Ok, a few words on the setup (can't change it!). I am using the GNU arm embedded toolchain (`gnu-arm-none-eabi-`) and the GNU ISO C99 (`gnu99`). – krnd Dec 13 '17 at 12:50
  • Well if different people define their flags differently, it might be time for you to define how your flags are supposed to be. If you require a flags definition as empty define, you can just compile-time hard fail if the defined symbol has any content. Same story the other way around - make the compiler enforce your defined style instead of accepting all kinds of definition. – grek40 Dec 13 '17 at 12:59
  • That's not working for me. It's a collection of student powered project with ~20 different students (one is me) every semester and I am writing the communication module to connect them. – krnd Dec 13 '17 at 14:39
  • You can use a set of PASTE() macros (it takes a second macro to expand) ... or you can just use `#if foo` ... to accomplish the same effect. – technosaurus Dec 14 '17 at 08:58

3 Answers3

2

You can stringify the macro name (if undefined) or the expansion (if defined) and then examine it. This program:

#define DefinedBlank
#define DefinedZero     0
#define DefinedOne      1

#define Stringify(x)    #x
#define Enabled(x)      (Stringify(x)[0] == 0 || Stringify(x)[0] == '1')

#include <stdio.h>

int main(void)
{
    const char *String[] = { "not ", "" };

    printf("Undefined is %senabled.\n",    String[Enabled(Undefined)]);
    printf("DefinedBlank is %senabled.\n", String[Enabled(DefinedBlank)]);
    printf("DefinedZero is %senabled.\n",  String[Enabled(DefinedZero)]);
    printf("DefinedOne is %senabled.\n",   String[Enabled(DefinedOne)]);
}

prints:

Undefined is not enabled.
DefinedBlank is enabled.
DefinedZero is not enabled.
DefinedOne is enabled.

Apple LLVM evaluates the conditions at compile time, so you could use if Enabled(foo) { code… }, and the code… would not be included in the executable if foo were not enabled. (However, it must be syntactically and grammatically correct.)

If you want more sophisticated distinctions than just undefined, blank, 0, or 1, then you can write an arbitrary routine to examine the string and return a result. Of course, the compiler is less likely to optimize that.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Having an additional macro that is useable in C code statements (as opposed to preprocessor conditionals) is definitely a nice thing. Side Note: In Visual Studio, there is a bug (?) in the preprocessor. It will complain about an empty parameter to `Stringify` when using `DefinedBlank`. A hack fix would be to change the parameter list `#define Stringify(x, _) #x` and usage `(Stringify(x,)[0] == 0 || Stringify(x,)[0] == '1')`. – grek40 Dec 13 '17 at 16:04
1

Quoting some GCC docs: If the defined operator appears as a result of a macro expansion, the C standard says the behavior is undefined. GNU cpp treats it as a genuine defined operator and evaluates it normally.

Meaning, you can't generally hide the defined macro test within some nested macro expansion, unless you rely on compiler specific non-standard extensions.

So, the best I could come up with involves a two-part test (https://stackoverflow.com/a/13262601/5265292 gave me the basic idea):

// if x is empty:            1 - -1 == 2
// if x is zero:             1 - 0 - 1 == 0
// if x is any other number: 1 - x - 1 == -x
#define ENABLED(x) (1 - x -1 != 0)


#if defined FLAG && ENABLED(FLAG)
// execute if FLAG is defined as not-zero
#endif

Complete sample:

#include <stdio.h>

#define TEST1
#define TEST2 0
#define TEST3 1
// define TEST4 // not defined

#define ENABLED(x) (1 - x -1 != 0)

int main(void)
{

#if defined TEST1 && ENABLED(TEST1)
    printf("TEST1 is enabled!\n");
#endif

#if defined TEST2 && ENABLED(TEST2)
    printf("TEST2 is disabled!\n");
#endif

#if defined TEST3 && ENABLED(TEST3)
    printf("TEST3 is enabled!\n");
#endif

#if defined TEST4 && ENABLED(TEST4)
    printf("TEST4 is disabled!\n");
#endif

    return 0;
}

As pointed out, you might be able to utilize the GNUCC specific macro extensions to have the defined test within the ENABLED test, but I don't have GCC and the time at hand to check this aspect and I prefer portable solutions.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • ^^ This is an insaneously smart preprocessor exploit :). It might solve my problem. - I really like the idea! – krnd Dec 13 '17 at 14:46
0

This provides a preprocessor test for the cases where a macro is undefined, defined but blank, defined to be “0”, or defined to be “1”. It does not distinguish other definitions for the macros (e.g., a macro defined to be “2” will be reported as not enabled):

#define EnabledTest     1
#define EnabledTest0    0
#define EnabledTest1    1
#define EnabledPaste(x) EnabledTest ## x
#define Enabled(x)      EnabledPaste(x)


#define DefinedBlank
#define DefinedZero     0
#define DefinedOne      1


#include <stdio.h>

int main(void)
{
    #if Enabled(Undefined)
        printf("Undefined is enabled.\n");
    #endif
    #if Enabled(DefinedBlank)
        printf("DefinedBlank is enabled.\n");
    #endif
    #if Enabled(DefinedZero)
        printf("DefinedZero is enabled.\n");
    #endif
    #if Enabled(DefinedOne)
        printf("DefinedOne is enabled.\n");
    #endif
}

the output is:

DefinedBlank is enabled.
DefinedOne is enabled.

Additionally, we can combine it with grek40’s idea to generalize it to undefined, defined but blank, and defined to strings that evaluate simply in place of x in 1 - x - 1:

#define EnabledTest     1
#define EnabledPaste(x) EnabledTest ## x
#define Enabled(x)      (EnabledPaste(x)) || (1 - x - 1)


#define DefinedBlank
#define DefinedZero     0
#define DefinedOne      1


#include <stdio.h>

int main(void)
{
    #if Enabled(Undefined)
        printf("Undefined is enabled.\n");
    #endif
    #if Enabled(DefinedBlank)
        printf("DefinedBlank is enabled.\n");
    #endif
    #if Enabled(DefinedZero)
        printf("DefinedZero is enabled.\n");
    #endif
    #if Enabled(DefinedOne)
        printf("DefinedOne is enabled.\n");
    #endif
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312