我可以把这个要求变成一个宏吗?

Can I turn this requirement into a macro?

我有带递减软件计数器的 C 程序。例如,如果我想每 2 秒闪烁一个 LED,我可以这样做:

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 ... while(0)。此 'trick' 滥用编译器的优化功能来创建某个宏,否则这是不可能的。

This site

阐明了这种方式。

有没有办法使用某种'macro trick'来实现我想要的?

再一次,这就是转变:

// 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

像“不,根本做不到”这样的回答也会被接受(前提是你知道自己在说什么)

编辑: 我需要补充一点,因为似乎并不是每个人都知道我想要什么,最后给出的答案甚至没有针对手头的具体问题。不知何故切换 IO 突然变得很重要?因此,我修改了代码示例以更好地说明问题所在。

编辑2: 我同意 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

我想我应该在美国用 EXIT 或更高级的东西交换 return true;。 调用这些状态的状态机如下所示:

#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

唯一没有生成的是'nextState()'函数之前的'if'和'else'。这些'flow conditions'需要填写

如果向用户提供了一个小示例或解释,他应该可以毫无困难地填写状态。他还应该能够手动添加状态。

我什至想用宏来交换这个:

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

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

我为什么要这样做?将不相关的信息隐藏在显示之外。删除状态机中的许多选项卡。通过使用简单的语法来提高整体可读性。与第 3 个库函数一样,您需要知道如何使用代码,而不是知道该函数是如何实现的。

您不需要知道状态如何发出准备就绪的信号。知道所讨论的函数用作状态函数比知道它 returns 有点变量更重要。

我也在使用前测试宏。所以我不会向某人提供可能表现出奇怪行为的状态机。

这里没有必要使用宏,这样做会导致非常不惯用的 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);

(我根据您的示例猜测合适的参数名称;适当调整。)

此外,看起来 ledTledPin 总是成对且属于一起的,在这种情况下,您应该考虑将它们放入 struct:

struct led {
    pin_t pin;
    int interval;
};

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

或者类似的东西。

#define LL(ledT) do {if(!ledT) { ledT = 200; TOG(ledPin); }}while(0)

Whilst reading about macros, I've read something interesting about using: do ... while(0). This 'trick' abuses the compiler's optimization feature to create a certain macro, which would otherwise be impossible.

里面的大部分观点其实都是错误的。没有什么关于优化。

主要原因是要使宏完全使用花括号进行编译。

这个编译不了

#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

鉴于这是一些旧的 8051 遗留项目,极不可能需要为引脚 I/O 处理创建抽象层宏。你只会有那么多别针。您的原始代码很可能是最好和最清晰的代码。

如果您出于某种原因担心代码重复,因为您有 product/support 具有不同布线等的多个 PCB 的多种组合,并且您受困于当前的代码库...那么 作为最后的手段,您可以使用宏来避免代码重复。这也假设您是一位经验丰富的 C 程序员 - 否则请停止阅读这里。

在这种罕见的情况下,您将看到的可能是所谓的 "X macros",它是关于声明整个预处理器常量列表的。然后,每当您需要做一些重复的事情时,您就会调用该列表并使用其中您对该特定调用感兴趣的常量。每个调用都是通过指定宏 "X" 在该特定调用中应该做什么来完成的,然后取消定义宏。

例如,如果您有端口 A、B、C,您在端口 A:0、B:1 和 C:2 上分别有 LED,并且希望每个引脚使用不同的延迟,您可以这样声明一个列表:

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

然后当你需要做重复性的工作时,可以调用这个列表。例如,如果这些端口有数据方向寄存器,则需要进行相应设置,这些寄存器称为 DDRA、DDRB、DDRC(以 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

(我用简单的按位异或替换了切换宏)

这将扩展为:

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 位计数器。

免责声明:我不建议使用此解决方案。

我试着把它变成宏。这确实是可能的,但如果它更快,那是另一个问题。每次调用宏时都会创建一个新变量。

#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上的测试交给你了。