0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

当使用参数调用宏时,会将参数替换为宏主体

Q4MP_gh_c472c21 来源:技术让梦想更伟大 作者:李肖遥 2020-11-16 16:41 次阅读

语法错误

当使用参数调用宏时,会将参数替换为宏主体,并与其他输入文件一起检查结果,以进行更多的宏调用,可以将部分来自宏主体和部分自变量的宏调用组合在一起。例如,

#definetwice(x)(2*(x)) #definecall_with_1(x)x(1) call_with_1(twice) //x=1 →twice(1) →(2*(1))

宏定义不必带有括号,通过在宏主体中编写不平衡的开放括号,可以创建一个从宏主体内部开始但在宏主体外部结束的宏调用。例如,

#definestrange(file)fprintf(file,"%s%d", … strange(stderr)p,35) →fprintf(stderr,"%s%d",p,35)

组合宏调用的功能可能会很有用,但是在宏主体中使用不平衡的开放括号只会造成混淆,应该避免。

运算符优先级问题

在大多数宏定义示例中,每次出现的宏参数名称都带有括号,并且另一对括号通常会包围整个宏定义,这是编写宏最好的方式。举个例子

#defineceil_div(x,y)(x+y-1)/y

假定其用法如下:

a=ceil_div(b&c,sizeof(int));

拓展开是

a=(b&c+sizeof(int)-1)/sizeof(int);

这没有达到我们的预期,C的运算符优先级规则使其等效于此,而我们想要的是:

a=(((b&c)+sizeof(int)-1))/sizeof(int);

如果我们将宏定义为

#defineceil_div(x,y)((x)+(y)-1)/(y)

可能导致另一种情况,sizeof ceil_div(1,2)是一个C表达式,可以计算ceil_div(1,2)类型的大小,它扩展为:

sizeof((1)+(2)-1)/(2)

这将采用整数的大小并将其除以2,而除法包含在内部的sizeof之外。所以整个宏定义的括号可防止此类问题。那么,下面是定义ceil_div的正确方法如下

#defineceil_div(x,y)((((x)+(y)-1)/(y))

吞噬分号

通常需要定义一个扩展为复合语句的宏。例如,考虑以下宏,该宏跨空格字符前进一个指针(参数p表示在何处查找):

#defineSKIP_SPACES(p,limit) {char*lim=(limit); while(p< lim) {                 if (*p++ != ' ') {                p--; break; }}}

该宏定义必须是单个逻辑行,严格来说,该调用扩展为复合语句,这是一个完整的语句,不需要用分号结束。

但是,由于它看起来像函数调用,因此,如果可以像使用函数调用一样使用它,则可以最大程度地减少混乱,然后再写一个分号,就像在SKIP_SPACES(p,lim)中一样。

这可能会在else语句之前出问题,因为分号实际上是空语句。假设你写

if(*p!=0) SKIP_SPACES(p,lim); else…

在if条件和else条件之间存在两个语句(复合语句和null语句)使C代码无效。

怎么解决?我们可以使用do…while语句更改宏SKIP_SPACES的定义以解决此问题。方法如下:

#defineSKIP_SPACES(p,limit) do{char*lim=(limit); while(p< lim) {                    if (*p++ != ' ') {                   p--; break; }}}           while (0)

SKIP_SPACES (p, lim);扩展为

do{…}while(0);

这是一个陈述,循环仅执行一次,而且大多数编译器不会为此生成任何额外的代码。

重复调用

我们常见的“最小”定义一个宏min,如下所示:

#definemin(X,Y)((X)< (Y) ? (X) : (Y))

当将此宏与包含副作用的参数一起使用时,如此处所示,

next=min(x+y,foo(z));

它扩展如下:

next=((x+y)< (foo (z)) ? (x + y) : (foo (z)));

其中x + y替换了X,而foo(z)替换了Y。

函数foo出现在程序中的语句中仅使用一次,但是表达式foo(z)已两次替换到宏扩展中。结果,执行该语句时可能会两次调用foo,所以min是一个不安全的宏。

解决此问题的最佳方法是以仅计算一次foo(z)值的方式定义min。C语言没有提供执行此操作的标准方法,但是可以使用GNU扩展来完成此操作,如下所示:

#definemin(X,Y) ({typeof(X)x_=(X); typeof(Y)y_=(Y); (x_< y_) ? x_ : y_; })

“({{…})”符号产生一个复合表达式,它的值是其最后一条语句的值。

如果不使用GNU C扩展,唯一的解决方案是在使用宏min时要小心。例如计算foo(z)的值时,将其保存在变量中,然后在min中使用该变量:

//假设foo返回int类型 #definemin(X,Y)((X)< (Y) ? (X) : (Y)) … {   int tem = foo (z);   next = min (x + y, tem); }

自引用宏

自引用宏是其名称出现在其定义中的宏。我们知道所有宏定义都将被重新扫描以查找更多要替换的宏,如果自引用被认为是宏的使用,它将产生无限大的扩展。

为防止这种情况,自引用不被视为宏调用。它原样传递到预处理器输出中。举个例子

#definefoo(4+foo)

按照普通规则,其宏定义分析如下

对foo的每个引用都将扩展为(4 + foo);

然后将对其进行重新扫描,并将其扩展为(4 +(4 + foo));

以此类推,直到计算机内存耗尽。

自引用规则将这一过程缩短了一步,即(4 + foo),因此此宏定义可能会导致程序在引用foo的任何地方将foo的值加4。

阅读程序的人看到foo是变量,就难以记得它也是宏,真的会坑爹的。它的一种常见有用用法是创建一个可扩展为其自身的宏。如果你写

#defineEPERMEPERM

然后宏EPERM扩展为EPERM。实际上,每当在运行文本中使用预处理器时,预处理器都会将其单独保留。

如果宏x扩展为使用宏y,而y的扩展引用了宏x,则这是x的间接自引用。在这种情况下,x也不展开,举个例子

#definex(4+y) #definey(2*x)

然后x和y扩展如下:

x→(4+y) →(4+(2*x)) y→(2*x) →(2*(4+y))

当每个宏出现在另一个宏的定义中时,它们将被展开,但是当它间接出现在其自己的定义中时,则不会被展开。

参数预扫描处理

宏参数在被替换为宏主体之前必须经过完全宏扩展,替换后,将再次扫描整个宏主体,包括替换的参数,以查找要扩展的宏。

如果参数包含任何宏调用,则它们将在第一次扫描时扩展,那么结果不包含任何宏调用,因此第二次扫描不会更改它。

如果按照给定的方式替换了参数,并且没有进行预扫描,则剩余的单个扫描将找到相同的宏调用并产生相同的结果。

预扫描处理在以下三种特殊情况下有大的作用。

对宏的嵌套调用

当宏的参数包含对该宏的调用时,就会发生对宏的嵌套调用,举个例子。

如果f是期望一个参数的宏,则f(f(1))是对f的嵌套调用对。通过扩展f(1)并将其代入f的定义来进行所需的扩展。预扫描会导致发生预期的结果。

如果没有预扫描,f(1)本身将被替换为参数,并且f的内部使用将在主扫描期间作为间接自引用出现,并且不会扩展。

调用其他可进行字符串化或连接的宏的宏

如果参数是字符串化或串联的,则不会进行预扫描。

如果要扩展宏,然后对其扩展进行字符串化或串联,则可以通过使一个宏调用进行该字符串化或串联的另一宏来实现。举个例子

#defineAFTERX(x)X_##x #defineXAFTERX(x)AFTERX(x) #defineTABLESIZE1024 #defineBUFSIZETABLESIZE

然后AFTERX(BUFSIZE)扩展为X_BUFSIZE,而XAFTERX(BUFSIZE)扩展为X_1024而不是X_TABLESIZE,预扫描始终会进行完整的扩展。

参数中使用的宏,其扩展名包含未屏蔽的逗号。

这可能导致使用错误数量的参数调用在第二次扫描时扩展的宏。举个例子

#definefooa,b #definebar(x)lose(x) #definelose(x)(1+(x))

我们预期的结果是bar(foo)变成(1 +(foo)),然后变成(1 +(a,b))。

然而bar(foo)扩展为loss(a,b)会出错,因为Los需要一个参数。在这种情况下,该问题可以通过使用相同的括号轻松解决,该括号应用于防止算术运算的错误嵌套:

#definefoo(a,b) or #definebar(x)lose((x))

多余的一对括号可防止foo定义中的逗号被解释为参数分隔符。

参数中的换行符

类似函数的宏的调用可以扩展到许多逻辑行,但是在本实施方式中,整个扩展是一行完成的。

因此,由编译器或调试器发出的行号是指调用在其上开始的行,这可能与包含导致问题的参数的行不同,例如:

#defineignore_second_arg(a,b,c)a;c ignore_second_arg(foo(), ignored(), syntaxerror);

由Syntax error on tokens触发的语法错误会导致错误消息引用第三行(ignore_second_arg行),即使有问题的代码来自第五行。

责任编辑:lq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4774

    浏览量

    68505
  • 变量
    +关注

    关注

    0

    文章

    613

    浏览量

    28359
  • 语法
    +关注

    关注

    0

    文章

    44

    浏览量

    9807

原文标题:避免这7个误区,才能让【宏】削铁如泥

文章出处:【微信号:gh_c472c2199c88,微信公众号:嵌入式微处理器】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    李彦宣布:百度文心大模型日调用量超15亿

    在百度世界2024大会上,百度公司创始人李彦宣布了一项令人瞩目的数据:百度文心大模型的日调用量已经超过15亿次。这一数据不仅彰显了百度在人工智能领域的领先地位,也预示着人工智能技术的广泛应用
    的头像 发表于 11-12 10:40 200次阅读

    SV中define定义的用法

    SV中使用预处理指令`define来定义可以用来创建文本替换。根据场景不同,`define主要用来定义常量、简化复杂的表达式或代码段以及提高代码的可移植性。其基本语法为:
    的头像 发表于 10-21 14:22 514次阅读

    科技与德赛电池签约储能电站合作项目

    科技和德赛电池储能电站合作项目签约仪式在胜科技隆重举行。胜科技董事长陈涛、惠州德赛电池董事长曾剑云出席签约仪式。胜科技CEO赵启祥、惠州德赛电池总裁程月帆代表双方签约。
    的头像 发表于 09-27 17:15 807次阅读

    行业领导莅临景智驾考察交流

    近日,中国人才研究会汽车人才专业委员会理事长 朱明荣先生、同济科技园常务副主任荣文伟先生一行莅临景智驾上海嘉定技术中心进行考察交流。 在深入了解景智驾公司发展情况后,各位领导对景智驾目前情况及未来发展给予了高度评价,并就
    的头像 发表于 09-03 09:37 439次阅读

    TINA仿真运放电路时,运放的参数中是否可以增加温度参数呢?

    我们目前在使用TINA仿真运放电路的参数,从模型中找到了外围电阻是有温度参数可以设定的,包括线性温度系数,二次温度系数,指数温度系数。但是运放本身的参数中目前没有找到与温度相关的参数
    发表于 08-14 07:31

    科技拟收购APCB 100%股权

    科技近期发布重要公告,宣布其计划通过全资子公司新加坡胜及PSL,以不超过2.787亿元人民币的现金,全面收购APCB Electronics(Thailand)Co.,Ltd.(简称APCB)的100%股权。此次收购标志着胜
    的头像 发表于 08-12 15:06 540次阅读

    LF412用PSPICE验证该运放的SPICE模型的遇到的疑问求解

    关于LF412,我用PSPICE验证该运放的SPICE模型的遇到以下问题: 1.该模型缺少电压噪声和电流噪声参数,有更加准确的SPICE模型吗; 2.该模型开环输出阻抗为纯阻性
    发表于 08-06 06:16

    请问如何为idf.py build指定特定参数

    如何为idf.py build指定特定参数: (1)build目录(即不是默认的build目录名称), (2)sdkconfig(即不是默认的文件名) 如何将基本的模块统一编译,而应用模块独立编译?因为有多个应用模块(代码是一套,只是用不同的隔开)
    发表于 06-12 08:07

    气体成功斩获无锡华润上华电子大宗载气项目,进军存量市场

    此次中标被视为气体领域重大突破,标志着成熟晶圆产线电子大宗载气存量业务的替换成为可能,从而开拓金电子大宗载气业务新的增长点。
    的头像 发表于 04-19 15:27 399次阅读

    证监会同意鑫科技创业板IPO注册

    证监会发布关于同意浙江鑫科技股份有限公司首次公开发行股票注册的批复,这标志着鑫科技在深交所创业板上市的脚步愈发临近。作为国内较早开始应用锻造工艺生产汽车铝合金车轮的高新技术企业,鑫科技在行业内具有显著的技术和市场优势。
    的头像 发表于 03-11 15:31 549次阅读

    鑫科技即将挂牌上市

    浙江鑫科技股份有限公司(以下简称“鑫科技”)成功获得证监会的批文,即将挂牌上市,这标志着鑫科技将成为资本市场锻造铝合金车轮第一股,迎来产业经营和资本运营双轮驱动的全新发展阶段。
    的头像 发表于 03-11 15:30 770次阅读

    CubeMx生成的stm32f013vet6设备,对于SDIOCLK频率设置的定义与手册里面对不上是为什么?

    在手册里面有SDIO adapter clock (SDIOCLK = HCLK) HCLK设置为72MHz的时候进行配置SDIO_CK, 通过寄存器SDIO_CLKCR的位7:0(CLKDIV
    发表于 03-08 08:29

    如何利用Rust过程实现derive-with库呢?

    通过派生 #[derive(With)] 给结构体字段生成 with_xxx 方法,通过链式调用 with_xxx 方法来构造结构体。
    的头像 发表于 01-25 09:51 297次阅读

    EG网关串口连接永PLC应用案例

    EG网关串口连接永PLC应用案例 前言:永PLC是一款国产优秀的可编程控制器,广泛应于工业控制领域,是一款性能高,运行稳定的控制器。此次我们要把永FBs/B1系列PLC通过Modbus协议连接
    的头像 发表于 01-03 14:45 603次阅读
    EG网关串口连接永<b class='flag-5'>宏</b>PLC应用案例

    linux内核系统调用参数传递

    与普通函数一样,系统调用通常需要一些输入/输出参数,这些参数可能包括实际值(即数字)、用户模式进程地址空间中的变量地址,甚至包括指向用户模式函数指针的数据结构的地址(参见第11章“信号相关的系统
    的头像 发表于 12-20 09:32 1598次阅读