new preprocessor trick to me

Viewed 93

(Below code is correct, not create by myself.) (it is inside the config of a motor board.)

#define BTN_TABLE(X) X(BUTTON, PA1)

#define BTN_X_EXTERNS(A, B) extern Button A;
BTN_TABLE(BTN_X_EXTERNS)

#define BTN_X_ID(A, B) A##_ID
#define BTN_X_ENUM(A, B) BTN_X_ID(A, B),
typedef enum { BTN_TABLE(BTN_X_ENUM) NUM_BOARD_BUTTONS } BoardButtonID;

#define BTN_X_FROM_ENUM(A, B) else if (button_id == BTN_X_ID(A, B)) return &A;
static __forceinline Button* button_from_enum(BoardButtonID button_id) {
    if (0) return 0;
    BTN_TABLE(BTN_X_FROM_ENUM)        
    else return 0;
}
  1. I don't get the meaning of if (0) return 0; and else return 0; in above example.
  2. why place the Marco in between these two line?
  3. I think we cannot place anything between if{} , else{} statement.
3 Answers

I think the intent behind this code was as follows:

  • We have some buttons; currently just one, but eventually there may be many more

  • Each button needs a corresponding global variable and a value in the BoardButtonID enum

  • We want a function which given the enum BoardButtonID value returns a pointer to the button's global variable

  • We want to achieve all this while only listing all buttons once.

@bolov has shown how the code expands. I'll note that one can add more buttons just by changing the definition of the BTN_TABLE macro:

#define BTN_TABLE(X) X(BUTTON, PA1) \
    X(ANOTHER_BUTTON, PA2) \
    X(YET_ANOTHER_BUTTON, PA3) \
    X(OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP, PA4)

The PA1, PA2, ... aren't actually used in this version of the code; maybe they would have been used for something later.

Now you can see the effect (I've reformatted the output):

extern Button BUTTON;
extern Button ANOTHER_BUTTON;
extern Button YET_ANOTHER_BUTTON;
extern Button OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP;

typedef enum { 
    BUTTON_ID,
    ANOTHER_BUTTON_ID,
    YET_ANOTHER_BUTTON_ID,
    OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP_ID,
    NUM_BOARD_BUTTONS 
} BoardButtonID;

static __forceinline Button* button_from_enum(BoardButtonID button_id) {
    if (0) 
        return 0;
    else if (button_id == BUTTON_ID) 
        return &BUTTON;
    else if (button_id == ANOTHER_BUTTON_ID) 
        return &ANOTHER_BUTTON;
    else if (button_id == YET_ANOTHER_BUTTON_ID)
        return &YET_ANOTHER_BUTTON;
    else if (button_id == OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP_ID) 
        return &OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP;
    else
        return 0;
}

And this makes it clear why the initial if is needed: the macro expansion in button_from_enum has no way to treat the first one specially. So it has to produce an else if for every button, including the first one, and the only way to make that valid is for there to be an if at the beginning. It needs to have a test that always fails, hence 0, and its corresponding "then" clause doesn't matter as it will never execute. The return 0 there may have just been chosen to shut up a compiler warning about the function possibly returning without a value. Of course, the return 0 in the final else clause can be reached, and serves as a default if someone passes a value that doesn't match any button.

You are right that if you put anything else in between the if and else, everything will break.

They could have defined it a little differently and used switch instead, which would have been slightly cleaner. I don't know why they didn't; maybe the compiler generates different code that they didn't like (e.g. a jump table that occupies more code space).

In any event, the resulting set of macros, while clever, are certainly not very easy to maintain. They should probably have considered writing a script instead that would generate the desired code from a simple list of buttons in a text file.

Or, they could have put the Button objects in an array instead of insisting on each one having its own variable. This would go nicely with their enum:

typedef enum { 
    BUTTON_ID,
    ANOTHER_BUTTON_ID,
    YET_ANOTHER_BUTTON_ID,
    OH_GOD_NOT_ANOTHER_BUTTON_MAKE_THEM_STOP_ID,
    NUM_BOARD_BUTTONS 
} BoardButtonID;

Button all_the_buttons[NUM_BOARD_BUTTONS];

static __forceinline Button* button_from_enum(BoardButtonID button_id) {
    if (button_id < NUM_BOARD_BUTTONS)
        return &all_the_buttons[button_id];
    else
        return NULL;
}

This way still only requires listing the buttons once, and it involves no macros at all.

This is one of the most unreadable pieces of code I have seen.

I personally don't have neither the time, energy or willingness to analyze and figure out these horible macros. So I just dumped the preprocessor output and this is the code presented to the compiler:

extern Button BUTTON;

typedef enum { BUTTON_ID, NUM_BOARD_BUTTONS } BoardButtonID;

static __forceinline Button* button_from_enum(BoardButtonID button_id) {
    if (0) return 0;
    else if (button_id == BUTTON_ID) return &BUTTON;
    else return 0;
}

it is inside the config of a motor board.

That explains the code. It is consistent with code that is generated by other software, rather than by a human. It is generated by some code that configures a software package to some target environment.

The purpose of code like this:

    if (0) return 0;
    BTN_TABLE(BTN_X_FROM_ENUM)        
    else return 0;

is to allow the generating code to put any number of else if lines between the if line and the else line. For example, in various circumstances, the generated code might be this:

    if (0) return 0;
    else return 0;

or this:

    if (0) return 0;
    BTN_TABLE(BTN_X_FROM_ENUM)        
    else return 0;

or this:

    if (0) return 0;
    BTN_TABLE(BTN_X_FROM_ENUM)        
    BTN_TABLE(BTN_Y_FROM_ENUM)        
    else return 0;

By using if (0) and else as bookends, the generating code is freed from having to have conditional cases such as “If there are zero conditions, just write return 0;. If there is one condition, write if (condition) return something; else return 0;. If there are multiple conditions, write if (first condition) return something; else if (second condition) return something;… else return 0;.

Instead, the generating code is simply:

  • Write if (0) return 0;.
  • For each condition, write an else if line for it (likely in the form of some BTN_TABLE macro use, the definition for which is emitted elsewhere in the generating code).
  • Write else return 0;.

Thus, while the resulting code is more complicated, the actual generating code is simpler.

  1. I don't get the meaning of if (0) return 0; and else return 0; in above example.

The if (0) is needed simply so that the following lines can be any number of else if statements. The return 0; is never executed and is simply needed to complete the if statement grammatically.

The else return 0; statement provides a default in case none of the conditions are met.

  1. why place the Marco in between these two line?

The generating code emits a macro invocation for every case it determines is needed in the target system.

  1. I think we cannot place anything between if{} , else{} statement.

Of course you can, the else if statements are proper there.

Related