打印
[C语言]

一个很变态的需求:一个#define能否产生多行#define?

[复制链接]
1704|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
mohanwei|  楼主 | 2016-7-1 15:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
如果使用了一个PA1来做I2C口的SDA,
需要为它定义端口、管脚、拉高、拉低、读取等一系列宏:
#define SDA_Port GPIOA
#define SDA_Pin  GPIO_Pin_1
#define Clr_SDA() GPIO_ResetBits(SDA_Port, SDA_Pin)
#define Set_SDA()   GPIO_SetBits(SDA_Port, SDA_Pin)
#define Get_SDA()   GPIO_ReadBit(SDA_Port, SDA_Pin)

如果有几十个IO,上面就要复制粘贴很多行,劳动量大不说,还容易眼花出错……
为了省事并且不会出错,想用一个宏定义来产生上面5行:
#define DefGPIO(a,b,c) ……在这里填写宏


使用时只需在h里添加两行:
DefGPIO(SDA,A,1) //把PA1定义为SDA

DefGPIO(SCL,B,2) //把PB2定义为SCL

然后在c里就能直接使用一堆操作:
Clr_SDA();
Set_SDA();
Set_SCL();
Clr_SCL();
而且上电初始化端口时还会用到SDA_Port,SDA_Pin,SCL_Port,SCL_Pin这些常数去设置管脚属性

相关帖子

沙发
znmcu| | 2016-7-1 16:34 | 只看该作者
可以啊,c的宏是支持连接和串化的

使用特权

评论回复
板凳
JasonWangSE| | 2016-7-1 16:43 | 只看该作者
是这个意思吗?
#define DefGPIO(port, pin)  (GPIO##port, GPIO_Pin_##pin)

#define SDA                 DefGPIO(A, 1)
#define SCL                 DefGPIO(B, 2)

#define Clr(gpio)           GPIO_ResetBits(gpio)
#define Set(gpio)           GPIO_SetBits(gpio)
#define Get(gpio)           GPIO_ReadBit(gpio)

使用特权

评论回复
地板
mohanwei|  楼主 | 2016-7-1 16:49 | 只看该作者
JasonWangSE 发表于 2016-7-1 16:43
是这个意思吗?
#define DefGPIO(port, pin)  (GPIO##port, GPIO_Pin_##pin)

不是……
使用时只需要在头文件里输入一行代码:
DefGPIO(SDA,A,1)

然后DefGPIO这个宏就会自动根据“SDA、A、1”这3个参数,展开得到5行宏定义:
#define SDA_Port GPIOA
#define SDA_Pin  GPIO_Pin_1
#define Clr_SDA() GPIO_ResetBits(SDA_Port, SDA_Pin)
#define Set_SDA()   GPIO_SetBits(SDA_Port, SDA_Pin)
#define Get_SDA()   GPIO_ReadBit(SDA_Port, SDA_Pin)

使用特权

评论回复
5
mohanwei|  楼主 | 2016-7-1 16:52 | 只看该作者
znmcu 发表于 2016-7-1 16:34
可以啊,c的宏是支持连接和串化的

有空您可以挑战一下……

#和##是能实现串化和连接功能,但是展开后总是报错,感觉关键在于如何展开后能得到‘#’这个特殊字符。

使用特权

评论回复
6
JasonWangSE| | 2016-7-1 19:55 | 只看该作者
mohanwei 发表于 2016-7-1 16:49
不是……
使用时只需要在头文件里输入一行代码:
DefGPIO(SDA,A,1)

我觉得你说的这种方式应该实现不了,预编译只负责把使用宏的地方做宏展开,应该不会识别宏里面还有嵌套的宏定义,你这种定义相当于使用了宏定义中的一个部分,类似于定义
#define COM_MACRO(a, b, c)    a = b+c; b=a+c; c=a+b;
然后在程序中直接使用b,想让他当成a+c一样

使用特权

评论回复
7
JasonWangSE| | 2016-7-1 20:15 | 只看该作者
mohanwei 发表于 2016-7-1 16:49
不是……
使用时只需要在头文件里输入一行代码:
DefGPIO(SDA,A,1)

stack overflow上有人讨论过这个问题,看到了这个回答:
Macros can't expand into preprocessing directives. From C99 6.10.3.4/3 "Rescanning and further replacement":

The resulting completely macro-replaced preprocessing token sequence is not processed as a preprocessing directive even if it resembles one,

使用特权

评论回复
8
ayb_ice| | 2016-7-1 20:39 | 只看该作者
只能定义多条语句,

使用特权

评论回复
9
conmajia| | 2016-7-1 21:50 | 只看该作者
做不到你的需求,老实点多写几行吧

使用特权

评论回复
10
Simon21ic| | 2016-7-1 22:57 | 只看该作者
应该不行,不过GPIO模拟IIC,可以用宏模板实现:
https://github.com/versaloon/ver ... APP/IIC/EMIIC_MOD.h

使用特权

评论回复
11
huarana| | 2016-7-1 23:26 | 只看该作者
可以用 #和 ##实现的 。

可移植性很强。  不过要对宏定义展开有深刻的理解才行 呵呵

使用特权

评论回复
12
lxyppc| | 2016-7-2 00:10 | 只看该作者
本帖最后由 lxyppc 于 2016-7-2 00:14 编辑

学boost_pp的
光用define不行,还得用include

使用特权

评论回复
13
mohanwei|  楼主 | 2016-7-2 08:51 | 只看该作者
lxyppc 发表于 2016-7-2 00:10
学boost_pp的
光用define不行,还得用include

根据提示深入发掘,还是没找到例子……能否具体一点?

其实问题的关键在于“#define”展开后能否存在‘#’这个特殊字符
主贴的问题可以简化为:
在头文件里编写一个宏:
#define _TestDef(x) ……在这里填空

然后紧跟着在头文件里输入一行代码:
_TestDef(1)   //带有参数“1”

它展开后应该得到2行:根据参数“1”创建两个新的宏
#define ValueA  (1*10)
#define ValueB  (1*20)

——想象里的,一般编译器不会输出给你看,但是在C文件里可以直接使用ValueA和ValueB

使用特权

评论回复
14
mohanwei|  楼主 | 2016-7-2 08:54 | 只看该作者
huarana 发表于 2016-7-1 23:26
可以用 #和 ##实现的 。

可移植性很强。  不过要对宏定义展开有深刻的理解才行 呵呵 ...

有没有兴趣挑战一下?
题目已简化到13楼……

使用特权

评论回复
15
mohanwei|  楼主 | 2016-7-2 09:04 | 只看该作者
关于#和##的常规使用方法,我的代码里有不少,如

//在这里修改版本号,然后重新编译:
#define Ver0 0 //版本:V0.2.1
#define Ver1 2
#define Ver2 1

//下面自动合并成“V0.2.1“这个字符串
#define _STR(s) #s
#define _Ver(a,b,c) _STR(a)##"."##_STR(b)##"."##_STR(c)

在C里定义一个字符串:
char *ProcName = "资产管理列控制器-V"""_Ver(Ver0,Ver1,Ver2)","__DATE__","__TIME__; //程序名称

使用时,先修改3个版本号字段,然后重新编译,程序里就可以显示产品名称、版本号、编译日期等,方便维护产品。
然而这种方法对主贴的问题是没有帮助的。

使用特权

评论回复
16
ofourme| | 2016-7-2 09:53 | 只看该作者
条件编译,如果满足条件,添加所需要的内容。可以把以上内容作为一个头文件,需要的地方include。

使用特权

评论回复
17
lxyppc| | 2016-7-2 09:57 | 只看该作者
仔细想了一下,楼主位的问题无解
对于#define   XXX          YYY
编译器在预处理时,只关会将搜索到的XXX,替换成YYY,也就是说在扫描代码的时候,遇到#define时,会马上把后面标识存到宏表里面
楼主位想要动态生成这个宏表,必须要在预处理之前,也就是说靠C自身的预处理机身完成不了这个任务
楼主为何不换个思路,这样来写你的代码

# include <boost/preprocessor/array.hpp>

#define   ARRAY_SDA   (2, (A, 1))
#define   ARRAY_SCL   (2, (A, 1))
#define  Port(x)  BOOST_PP_CAT(GPIO, BOOST_PP_ARRAY_ELEM(0, BOOST_PP_CAT(ARRAY_, x) ))
#define  Pin(x)   BOOST_PP_CAT(GPIO_Pin_,  BOOST_PP_ARRAY_ELEM(1, BOOST_PP_CAT(ARRAY_, x) ))
#define  Clr(x)   GPIO_ResetBits( Port(x), Pin(x) )
#define  Set(x)   GPIO_SetBits( Port(x), Pin(x) )
#define  Get(x)   GPIO_ReadBit( Port(x), Pin(x) )
void test(void)
{
    Clr(SDA);
    Set(SDA);
    Get(SCL);
}

展开后变成
void test(void)
{
    GPIO_ResetBits( GPIOA, GPIO_Pin_1 );
    GPIO_SetBits( GPIOA, GPIO_Pin_1 );
    GPIO_ReadBit( GPIOA, GPIO_Pin_1 );
}

使用特权

评论回复
18
落叶行健ywm| | 2016-7-2 10:58 | 只看该作者
JasonWangSE 发表于 2016-7-1 19:55
我觉得你说的这种方式应该实现不了,预编译只负责把使用宏的地方做宏展开,应该不会识别宏里面还有嵌套的 ...

说的很对!这种的话目前我还没看到过呢。不过一般某个外设不会有太多接口的。

使用特权

评论回复
19
mohanwei|  楼主 | 2016-7-3 11:30 | 只看该作者
lxyppc 发表于 2016-7-2 09:57
仔细想了一下,楼主位的问题无解
对于#define   XXX          YYY
编译器在预处理时,只关会将搜索到的XXX ...

难怪从没见过省事的写法……

看来在这个问题上,宏能提供的帮助有限,并且让代码可读性变差了很多……
我打算写个小工具,可以根据一段IO配置文本自动生成前面所需的h和c。相当于自定义了一个预处理器。ARM各个厂家的库源代码应该就是用这种原理自动打印出来的,就是没见过类似的工具的介绍

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

177

主题

9320

帖子

24

粉丝