我可以把这个要求变成一个宏吗?
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' 滥用编译器的优化功能来创建某个宏,否则这是不可能的。
阐明了这种方式。
有没有办法使用某种'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);
(我根据您的示例猜测合适的参数名称;适当调整。)
此外,看起来 ledT
和 ledPin
总是成对且属于一起的,在这种情况下,您应该考虑将它们放入 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);
}
鉴于这是一些旧的 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上的测试交给你了。
我有带递减软件计数器的 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' 滥用编译器的优化功能来创建某个宏,否则这是不可能的。
阐明了这种方式。
有没有办法使用某种'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);
(我根据您的示例猜测合适的参数名称;适当调整。)
此外,看起来 ledT
和 ledPin
总是成对且属于一起的,在这种情况下,您应该考虑将它们放入 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);
}
鉴于这是一些旧的 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上的测试交给你了。