添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I am trying to simplify the declaration of an array, but ran into an issue with the preprocessors that I am using. My initial code looks like the following:

#define REQ_ENTRY(parm_1, parm_2)    \
#if defined(parm_1)                  \
    { parm_1, parm_2 },              \
#endif
typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;
static const MyTypedef_t MyList[] =
    REQ_ENTRY( ID_1, 1 )
    REQ_ENTRY( ID_2, 2 )
    REQ_ENTRY( ID_3, 3 )
    REQ_ENTRY( ID_4, 4 )
    REQ_ENTRY( ID_5, 5 )

The build fails, of course, with the error message of "error: '#' is not followed by a macro parameter". The reason for this is explained here (Why compiler complain about this macro declaration)

Basically, I am trying to avoid declaring the array as the follows, which does work:

static const MyTypedef_t MyList[] =
    #if defined (ID_1)
    { ID_1, 1 },
    #endif
    #if defined (ID_2)
    { ID_2, 2 },
    #endif
    #if defined (ID_3)
    { ID_3, 3 },
    #endif
    #if defined (ID_4)
    { ID_4, 4 },
    #endif
    #if defined (ID_5)
    { ID_5, 5 },
    #endif        

The list can be rather long and vary depending upon the build type of the project. I have tried thinking of using x-macros, but I think I would have the same issue. I am hoping someone might see a way of creating the preprocessor macros in such a way that I could achieve the original sugar syntax? Any insight is greatly appreciated.

You can wrap #ifdef around a macro definition; you can't embed it inside one. Also, you can't directly test whether an argument to a macro is a defined macro. Unfortunately, the nay-saying is the easy part; it is not clear that there's a good way to avoid your original code. That's what I've used in equivalent circumstances. – Jonathan Leffler Sep 25, 2015 at 4:14

There is no nice clean solution. But there are solutions of varying ugliness.

If you don't mind including both the id and the sequence in the macro definition, it can be solved like this:

#define CONCAT2(x,y) x##y
#define CONCAT(x,y) CONCAT2(x,y)
#define REQ_ENTRY_YES(p1, p2) { p1 , p2 }
#define REQ_ENTRY_NO(p1) 
#define IS_PAIR_HELPER(a, b, c, ...) c
#define IS_PAIR(...) IS_PAIR_HELPER(__VA_ARGS__, YES, NO)
#define REQ_ENTRY(pair) CONCAT(REQ_ENTRY_, IS_PAIR(pair))(pair)
#define ID_1 78723649, 1
#define ID_3 2347602, 3
typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;
static const MyTypedef_t MyList[] =
    REQ_ENTRY( ID_1 )
    REQ_ENTRY( ID_2 )
    REQ_ENTRY( ID_3 )
    REQ_ENTRY( ID_4 )
    REQ_ENTRY( ID_5 )

Run through gcc with -std=c11 -Wall -E, and showing only the MyList definition:

static const MyTypedef_t MyList[] =
    { 78723649 , 1 }
    { 2347602 , 3 }

It's a pity that the defined operator is available only in the context of #if and #ifelse, but not for macro expansions. As it stands, I agree with rici about the solutions of varying ugliness.

Here's a solution that requires that the defined values be surrounded by parentheses. You can then use the ID as regular value and you can also pass it to DEF, which expands to either 1 when the macro is in parentheses or 0 if not. (That's a trick I learned here.)

With the help of the DEF macro, you can create auxiliary macros that expand or ignore the given definition:

/* Auxiliary macros */
#define M_CHECK(...) M_CHECK_(__VA_ARGS__)
#define M_CHECK_(a, b, ...) b
#define M_IS_PAREN(x) M_CHECK(M_IS_PAREN_ x, 0)
#define M_IS_PAREN_(...) 1, 1
#define M_CONCAT(a, b) M_CONCAT_(a, b)
#define M_CONCAT_(a, b) a ## b
/* Conditional definition macros */
#define DEF(x) M_IS_PAREN(x)
#define DEF_IF_0(id, def)
#define DEF_IF_1(id, def) {id, def},
#define COND_DEF(x, y) M_CONCAT(DEF_IF_, DEF(x))(x, y)
/* Implementation */
#define ID_1 (27)
#define ID_3 (28)
#define ID_4 (29)
static const MyTypedef_t MyList[] = {
    COND_DEF(ID_1, 1)
    COND_DEF(ID_2, 2)
    COND_DEF(ID_3, 3)
    COND_DEF(ID_4, 4)
    COND_DEF(ID_5, 5)

This will produce:

static const MyTypedef_t MyList[] = {
    {(27), 1},
    {(28), 3},
    {(29), 4},

You can also use the DEF macro in code, which will be expanded to either 0 or 1:

printf("ID_1 is %s.\n", DEF(ID_1) ? "defined" : "undefined");

This is the closest I was able to get without introducing redundancy:

#define PREPROCESSOR_IF #if
#define PREPROCESSOR_ENDIF #endif
#define PREPROCESSOR_NEWLINE /*
#define REQ_ENTRY(parm_1, parm_2)                         \
PREPROCESSOR_IF defined(parm_1)      PREPROCESSOR_NEWLINE \
    { parm_1, parm_2 },              PREPROCESSOR_NEWLINE \
PREPROCESSOR_ENDIF
typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;
static const MyTypedef_t MyList[] =
    REQ_ENTRY( ID_1, 1 )
    REQ_ENTRY( ID_2, 2 )
    REQ_ENTRY( ID_3, 3 )
    REQ_ENTRY( ID_4, 4 )
    REQ_ENTRY( ID_5, 5 )

You need to run the preprocessor only once using -E and -CC and then compile the result of the first preprocessor pass, however it does not work due to (for example)

#if defined (ID_1) /*
*/ { ID_1, 1 }, /*
*/ #endif

not being recognized as distinct lines for the preprocessor, since the comments are replaced by just one space.

I was able to come up with a solution using redundancy, but it is hardly better than what you proposed (writing out the whole statement each time):

#define PREPROCESSOR_IF #if
#define PREPROCESSOR_ENDIF #endif
#define PREPROCESSOR_DEFINE #define
#define PREPROCESSOR_NEWLINE /*
#define REQ_ENTRY_1(parm_1, parm_2) PREPROCESSOR_IF defined(parm_1)
#define REQ_ENTRY_2(parm_1, parm_2) { parm_1, parm_2 }, 
#define REQ_ENTRY_3(parm_1, parm_2) PREPROCESSOR_ENDIF
PREPROCESSOR_DEFINE ID_1 (27)
PREPROCESSOR_DEFINE ID_3 (28)
PREPROCESSOR_DEFINE ID_4 (29)
typedef struct {
    int parm1,
        parm2;
} MyTypedef_t;
static const MyTypedef_t MyList[] =
    REQ_ENTRY_1( ID_1, 1 )
    REQ_ENTRY_2( ID_1, 1 )
    REQ_ENTRY_3( ID_1, 1 )
    REQ_ENTRY_1( ID_2, 2 )
    REQ_ENTRY_2( ID_2, 2 )
    REQ_ENTRY_3( ID_2, 2 )
    REQ_ENTRY_1( ID_3, 3 )
    REQ_ENTRY_2( ID_3, 3 )
    REQ_ENTRY_3( ID_3, 3 )
    REQ_ENTRY_1( ID_4, 4 )
    REQ_ENTRY_2( ID_4, 4 )
    REQ_ENTRY_3( ID_4, 4 )
    REQ_ENTRY_1( ID_5, 5 )
    REQ_ENTRY_2( ID_5, 5 )
    REQ_ENTRY_3( ID_5, 5 )

This does compile as desired using the two-step compilation process described above.

I wanted to try this in my regular compilation process, unfortunately I get error: stray '#' in program note: in expansion of macro 'PREPROCESSOR_IF' – sdbbs Dec 22, 2022 at 19:31

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.