Могу ли я превратить это требование в макрос? - PullRequest
0 голосов
/ 07 января 2020

У меня есть C программы с уменьшающимися счетчиками программного обеспечения. Например, если я хочу мигать светодиодом каждые 2 секунды, я могу сделать:

if(!ledT) { 
    ledT = 200;
    // code
    // code
    // code 
}

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

if(!ledT) { ledT = 200;
    // code 
    // code 
    // code  
}

Для всей строки if вместо этого я хотел бы использовать макрос. Таким образом, код будет выглядеть примерно так:

expired(ledT, 200) {
    // code 
    // code 
    // code 
}

Я использую что-то подобное в своем коде конечного автомата для состояния входа.

if(runOnce) { runOnce = false;
    // code 
    // code 
    // code 

Желаемый синтаксис:

entryState {
    // code 
    // code 
    // code 

.

#define entryState if(runOnce) { runOnce = false; // this ofcourse cannot work But something like this is what I want.

Я сделал несколько попыток, но ничего не получилось. Проблема в том, что '{' находится где-то посередине макроса, и я хочу напечатать '{' позади макроса, потому что, как мы все знаем, ни один редактор кода не может жить с неравным количеством '{' и '}'

expired(ledT, 200); // expired is macro, not function
    // code
    // code
    // code
}

так что об этом не может быть и речи

Читая о макросах, я прочитал кое-что интересное об использовании: do ... пока (0). Этот «трюк» использует функцию оптимизации компилятора для создания определенного макроса, который в противном случае был бы невозможен.

Этот сайт

проливает некоторый свет на этот способ.

Есть ли способ использовать какой-то «макро-трюк» для достижения того, что я хотите?

и снова, это преобразует:

// this
if(runOnce) {
    runOnce = false;
    // code
    // code
    // code

// into this
entryState {
    // code
    // code
    // code

// and this:
if(!someTimer) {
    someTimer = someInterval;
    // code
    // code
    // code

// must be transformed into:
timeExpired(someTimer, someInterval) {
    // code
    // code
    // code

И ответ типа «Нет, это просто невозможно сделать» также будет принят (при условии, что вы знаете, о чем говорите )

РЕДАКТИРОВАТЬ: мне нужно добавить дополнение, потому что не все, кажется, знают, что я хочу, последний ответ даже не направлен на конкретную проблему c под рукой. Каким-то образом переключение IO вдруг стало важным? Поэтому я изменил свои примеры кода, чтобы лучше проиллюстрировать, в чем проблема.

EDIT2: я согласен, что макрос timeExpired вообще не улучшает читаемость

Чтобы показать, что некоторые макросы могут улучшить читабельность, я приведу фрагмент состояния и конечного автомата. Вот как сгенерированное состояние выглядит в моем коде:

State(stateName) {
    entryState {
        // one time only stuff
    }
    onState {
        // continous stuff
        exitFlag = true; // setting this, exits the state
    }
    exitState {
        // one time only stuff upon exit
        return true;
    }
}

В настоящее время используется с этими макросами:

#define State(x) static bool x##F(void)
#define entryState if(runOnce) 
#define onState runOnce = false;
#define exitState if(!exitFlag) return false; else

Я думаю, мне следует обменять return true; в состояниях на EXIT или что-то более приличное. И конечный автомат, который вызывает эти состояния, выглядит следующим образом:

#undef State

#define State(x) break; case x: if(x##F())
extern bit weatherStates(void) {
    if(enabled) switch(state){
        default: case weatherStatesIDLE: return true;

        State(morning) {
            if(random(0,1)) nextState(afternoon, 0);
            else            nextState(rain, 0); }

        State(afternoon) {
            nextState(evening, 0); }

        State(evening) {
            if(random(0,1)) nextState(night, 0);
            else            nextState(thunder, 0); }

        State(night) {
            nextState(morning, 0); }

        State(rain) {
            nextState(evening, 0); }

        State(thunder) {
            nextState(morning, 0); }

        break; }
    else if(!weatherStatesT) enabled = true; 
    return false; }
#undef State

Единственное, что не генерируется, это «if» и «else» перед функциями «nextState ()». Эти «условия потока» необходимо заполнить.

Если пользователю предоставляется небольшой пример или объяснение, у него не должно возникнуть никаких проблем с заполнением состояний. Он также должен иметь возможность добавлять состояния вручную.

Я бы даже хотел обменять это макросами:

extern bit weatherStates(void) {
        if(enabled) switch(state){
            default: case weatherStatesIDLE: return true;

и

break;} }
    else if(!weatherStatesT) enabled = true;
    return false;}

Зачем мне это делать? это? Чтобы скрыть неактуальную информацию из вашего дисплея. Чтобы убрать много вкладок в автомате. Для повышения общей читабельности с помощью простого синтаксиса. Как и в случае с третьими библиотечными функциями, вам нужно знать, как использовать код, а не знать, как эта функция работает.

Вам не нужно знать, как состояние сигнализирует о том, что оно готово. Более важно знать, что рассматриваемая функция используется как функция состояния, чем знать, что она возвращает битовую переменную.

Также я тестирую макросы перед использованием. Поэтому я не предоставляю кому-то конечные автоматы, которые могут показывать странное поведение.

Ответы [ 4 ]

5 голосов
/ 07 января 2020

Здесь нет необходимости использовать макросы, и это приводит к крайне не-идиоматическому c C коду, который на самом деле не имеет никаких преимуществ по сравнению с правильным C кодом.

Использование вместо этого:

int toggle_if_unset(int time, int pin, int interval) {
    if (time == 0) {
        time = 200;
        TOG(pin);
    }
    return time;
}
ledT = toggle_if_unset(ledT, ledPin, 200);

(я предполагаю соответствующие имена параметров, основанные на вашем примере; настройте соответствующим образом.)

Более того, это выглядит так, как будто ledT и ledPin всегда спарены и принадлежат друг другу, и в этом случае вам следует рассмотреть возможность помещения их в struct:

struct led {
    pin_t pin;
    int interval;
};

void toggle_if_unset(struct led *led, int new_interval);

или что-то в этом роде.

4 голосов
/ 07 января 2020

Учитывая, что это для какого-то старого унаследованного проекта 8051, крайне маловероятно , что вам понадобится создать макросы абстракционного уровня для обработки выводов ввода / вывода. У вас будет только так много булавок. Ваш исходный код, скорее всего, самый лучший и понятный.

Если вы по какой-то причине беспокоитесь о повторении кода, потому что у вас есть несколько комбинаций продукта / поддержки с несколькими печатными платами с разной маршрутизацией и т. Д. c, и вы застряли с вашей текущей кодовой базой ... тогда в крайнем случае вы можете использовать макросы, чтобы избежать повторения кода. Это также предполагает, что вы опытный C программист - иначе прекратите читать здесь.

В этом редком сценарии вы, вероятно, увидите что-то, известное как «X macros», что означает объявление весь список констант препроцессора. Затем, когда вам нужно сделать что-то повторяющееся, вы вызываете этот список и используете внутри него константы, которые вас интересуют для этого конкретного c вызова. Каждый вызов выполняется путем указания того, что должен делать макрос «X» в этом конкретном вызове, а затем неопределенный макрос впоследствии.

Например, если у вас есть порты A, B, C, у вас есть светодиоды на порту A: 0, B: 1 и C: 2 соответственно и wi sh для использования разных задержек на вывод, вы можете объявить список следующим образом:

#define LED_LIST           \
/*  port   pin   delay */  \
  X(A,     0,    100)      \
  X(B,     1,    200)      \
  X(C,     2,    300)      \

Затем вы можете вызвать этот список когда вам нужно делать повторяющиеся задачи. Например, если эти порты имеют регистры направления данных, которые необходимо установить соответствующим образом, и эти регистры называются DDRA, DDRB, DDR C (с использованием именования Motorola / AVR в качестве примера):

/* set data direction registers */
#define X(port, pin, delay) DDR##port |= 1u<<pin;
  LED_LIST
#undef X

Это расширится до :

DDRA |= 1u<<0;
DDRB |= 1u<<1;
DDRC |= 1u<<2;

Аналогично, вы можете инициализировать счетчики как:

/* declare counters */
#define X(port, delay) static uint16_t count##port = delay;
  LED_LIST
#undef X


...

/* check if counters elapsed */
#define X(port, delay) if(count##port == 0) { count##port = delay; PORT##port ^= 1u << pin; }
  LED_LIST
#undef X

(я заменил макрос переключения на простой битовый XOR)

, который расширится до :

static uint16_t countA = 100;
static uint16_t countB = 200;
static uint16_t countC = 300;

...

if(countA == 0) 
{ 
  countA = 100; 
  PORTA ^= 1u << 0; 
}
if(countB == 0) 
{ 
  countB = 200; 
  PORTB ^= 1u << 1; 
}
if(countC == 0) 
{ 
  countC = 300; 
  PORTC ^= 1u << 2; 
}

И, конечно же, избегайте использования 16-битных счетчиков, как здесь, за исключением случаев, когда вы работаете с паршивым 8-битным.

1 голос
/ 07 января 2020
#define LL(ledT) do {if(!ledT) { ledT = 200; TOG(ledPin); }}while(0)

Читая о макросах, я прочитал кое-что интересное об использовании: do ... while (0). Этот «трюк» использует функцию оптимизации компилятора для создания определенного макроса, который в противном случае был бы невозможен.

Большинство представленных здесь мнений на самом деле неверны. Оптимизация ничего не дает.

Основная причина - сделать макросы с использованием свернутых скобок для компиляции вообще.

Этот не скомпилирует

#define A(x) {foo(x);bar(x);}

void foo1(int x)
{
    if (x) A(1);
       else B(0);
}

, но скомпилирует

#define A(x) do{foo(x);bar(x);}while(0)

void foo1(int x)
{
    if (x) A(1);
       else B(0);
}

https://godbolt.org/z/4jH2jP

0 голосов
/ 07 января 2020

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я не рекомендую использовать это решение.

У меня было go при попытке превратить это в макрос. Это действительно возможно, но если это быстрее, это другой вопрос. Когда вы создаете новую переменную каждый раз, когда вызываете макрос.

#include <stdio.h>

#define entryState(runOnce) int temp_state = runOnce; if (runOnce) runOnce = 0; if (temp_state)
#define timeExpired(someTimer, someInterval) int temp_expired = someTimer; if (!someTimer) someTimer = someInterval; if (!temp_expired)

int main(int argc, const char* argv[]) {

    int runOnce = 1;
    int someTimer = 0;
    int someInterval = 200;
    timeExpired(someTimer, someInterval) {
        printf("someTimer is Expired\n");
    }

    printf("someTimer: %i\n\n", someTimer);

    entryState(runOnce) {
        printf("this is running once\n");
    }

    printf("runOnce: %i\n", runOnce);

}

Компиляция и запуск:

c:/repo $ gcc test.c -o test
c:/repo $ ./test.exe 
someTimer is Expired
someTimer: 200

this is running once
runOnce: 0

У меня сейчас нет компилятора C51, поэтому я позволю тестирование на 8051 передано вам.

...