новый трюк препроцессора для меня - PullRequest
2 голосов
/ 18 июня 2020

(Код ниже правильный, не создаваемый мной.) (Он находится внутри конфигурации моторной платы.)

#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. Я не понимаю значения if (0) return 0; а иначе верните 0; в приведенном выше примере.
  2. зачем помещать Marco между этими двумя строками?
  3. Я думаю, что мы не можем разместить что-либо между if{} , else{} оператором.

Ответы [ 3 ]

4 голосов
/ 18 июня 2020

Думаю, за этим кодом стояла следующая цель:

  • У нас есть несколько кнопок; в настоящее время только один, но со временем их может быть намного больше

  • Каждой кнопке нужна соответствующая глобальная переменная и значение в BoardButtonID перечислении

  • Нам нужна функция, которая с заданным значением enum BoardButtonID возвращает указатель на глобальную переменную кнопки

  • Мы хотим достичь всего этого, перечисляя все кнопки только один раз.

@ bolov показал, как расширяется код. Замечу, что можно добавить больше кнопок, просто изменив определение макроса BTN_TABLE:

#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)

PA1, PA2, ... фактически не используются в этой версии кода; возможно, они были бы использованы для чего-то позже.

Теперь вы можете увидеть эффект (я переформатировал вывод):

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;
}

И это делает его Понятно, зачем нужен начальный if: расширение макроса в button_from_enum не имеет возможности обрабатывать первый специально. Таким образом, он должен создать else if для каждой кнопки, включая первую, и единственный способ сделать это действительным - это наличие if в начале. У него должен быть тест, который всегда терпит неудачу, следовательно, 0, и соответствующее ему предложение «then» не имеет значения, поскольку оно никогда не будет выполнено. return 0 там, возможно, был просто выбран, чтобы отключить предупреждение компилятора о возможности возврата функции без значения. Конечно, можно достичь return 0 в последнем предложении else, и оно будет использоваться по умолчанию, если кто-то передаст значение, не соответствующее какой-либо кнопке.

Вы правы, что если вы поместите что-нибудь иначе между if и else все сломается.

Они могли бы определить его немного по-другому и использовать вместо него switch, что было бы немного чище. Я не знаю, почему они этого не сделали; возможно, компилятор сгенерирует другой код, который им не понравился (например, таблица переходов, занимающая больше места для кода). . Возможно, им стоило подумать о написании сценария, который генерировал бы желаемый код из простого списка кнопок в текстовом файле.

Или они могли бы поместить Button объекты в массив вместо того, чтобы настаивать на том, чтобы каждый из них имел свою собственную переменную. Это будет go хорошо с их 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;
}

Этот способ по-прежнему требует только один раз перечислить кнопки и не включает никаких макросов вообще.

3 голосов
/ 18 июня 2020

Это один из самых нечитаемых фрагментов кода, которые я видел.

Лично у меня нет ни времени, ни энергии, ни желания анализировать и разгадывать эти ужасные макросы. Итак, я просто сбросил вывод препроцессора, и это код, представленный компилятору:

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;
}
2 голосов
/ 19 июня 2020

это внутри конфигурации моторной платы.

Это объясняет код. Он согласуется с кодом, который создается другим программным обеспечением, а не человеком. Он генерируется некоторым кодом, который настраивает программный пакет для некоторой целевой среды.

Цель такого кода:

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

- позволить генерирующему коду помещать любое количество else if строк между строкой if и строкой else. Например, в различных обстоятельствах сгенерированный код может быть таким:

    if (0) return 0;
    else return 0;

или так:

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

или так:

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

Используя if (0) и else в качестве подставки, генерирующий код освобождается от необходимости иметь условные случаи, такие как «Если есть нулевые условия, просто напишите return 0;. Если есть одно условие, напишите if (condition) return something; else return 0;. Если имеется несколько условий, напишите if (first condition) return something; else if (second condition) return something;… else return 0;.

Вместо этого код генерации будет просто:

  • Запишите if (0) return 0;.
  • Для каждого условия напишите строка else if для него (вероятно, в форме использования некоторого макроса BTN_TABLE, определение для которого генерируется в другом месте генерируемого кода).
  • Write else return 0;.

Таким образом, хотя результирующий код более сложен, фактический генерирующий код проще.

  1. Я не понимаю значение if (0) return 0; и else return 0; в приведенном выше примере.

if (0) нужен просто для того, чтобы следующие строки могли быть любым количеством операторов else if. Оператор return 0; никогда не выполняется и просто необходим для грамматического завершения оператора if.

Оператор else return 0; обеспечивает значение по умолчанию в случае, если ни одно из условий не выполнено.

зачем помещать Marco между этими двумя строками?

Генерирующий код испускает вызов макроса для каждого случая, когда он определяет необходимость в целевой системе.

Я думаю, что мы не можем поместить что-либо между if{} , else{} оператором.

Конечно, можете, здесь правильные операторы else if.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...