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

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

3天内不再提示

工程师避免这些误区才能让【宏】削铁如泥

工程师 来源:嵌入式资讯精选 作者:嵌入式资讯精选 2020-10-23 15:15 次阅读

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

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

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

#define strange(file) fprintf (file, “%s %d”,…strange(stderr) p, 35) → fprintf (stderr, “%s %d”, p, 35)

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

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

#define ceil_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);

如果我们将宏定义为

#define ceil_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的正确方法如下

#define ceil_div(x,y)((((x)+(y)-1)/(y))

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

#define SKIP_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的定义以解决此问题。方法如下:

#define SKIP_SPACES(p, limit) \do { char *lim = (limit); \ while (p 《 lim) { \ if (*p++ != ‘ ’) { \ p--; break; }}} \while (0)

SKIP_SPACES (p, lim);扩展为

do {…} while (0);

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

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

#define min(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扩展来完成此操作,如下所示:

#define min(X, Y) \({ typeof (X) x_ = (X); \ typeof (Y) y_ = (Y); \ (x_ 《 y_) ? x_ : y_; })

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

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

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

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

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

#define foo (4 + foo)

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

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

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

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

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

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

#define EPERM EPERM

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

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

#define x (4 + y)#define y (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的内部使用将在主扫描期间作为间接自引用出现,并且不会扩展。

调用其他可进行字符串化或连接的宏的宏如果参数是字符串化或串联的,则不会进行预扫描。

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

#define AFTERX(x) X_ ## x#define XAFTERX(x) AFTERX(x)#define TABLESIZE 1024#define BUFSIZE TABLESIZE

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

参数中使用的宏,其扩展名包含未屏蔽的逗号。这可能导致使用错误数量的参数调用在第二次扫描时扩展的宏。举个例子

#define foo a,b#define bar(x) lose(x)#define lose(x) (1 + (x))

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

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

#define foo (a,b)or#define bar(x) lose((x))

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

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

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

#define ignore_second_arg(a,b,c) a; cignore_second_arg (foo (), ignored (), syntax error);

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

责任编辑:haq

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

    关注

    3

    文章

    4350

    浏览量

    63076
  • 编译器
    +关注

    关注

    1

    文章

    1643

    浏览量

    49348
  • 宏汇编器
    +关注

    关注

    0

    文章

    7

    浏览量

    9002
收藏 人收藏

    评论

    相关推荐

    工程师在电路设计中的八大误区

    我们常常会发现,自己想当然的一些规则或道理往往会存在一些差错。电子工程师在电路设计中也会有这样的例子。下面是一位工程师总结的八大误区点。
    发表于 03-16 17:04 1633次阅读

    电子工程师之路

    ,不应该一直沉默寡言。假如你能正确的明白自己缺少什么条件,怎么去满足这些条件,我想你基本就要成功了。本人不才,简单的说下了。本人喜欢交工程师朋友。有愿意和哥们交朋友的还请留言。
    发表于 11-27 14:02

    工程师手记:FPGA学习的四大误区

    工程师手记:FPGA学习的四大误区
    发表于 08-17 23:47

    新手学习PADS的三个误区

    很多新手经常纠结的一件事就是学习PADS是不是可以拿高薪,是不是可以成为一个工程师,其实这些都是很大的误区误区大致都是在以下几种情况第一:我很熟练的掌握了PADS工具,其实PADS与
    发表于 07-30 17:10

    如何才能让自己成为一名嵌入式开发工程师

    却比比皆是。面对一个如此朝阳的行业,我们如何才能让自己成为一名嵌入式开发工程师?成为一名优秀的嵌入式开发工程师有哪些要求?  我们都知道,嵌入式主要由硬件和软件两部分组成,硬件(微处理器硬件)是软件
    发表于 11-08 07:40

    如何才能成为一个硬件工程师

    如何才能成为一个硬件工程师
    发表于 11-11 07:11

    如何才能让自己成为一名优秀的嵌入式开发工程师

      在互联网行业中,嵌入式技术后来居上,因为物联网和人工智能的发展,嵌入式技术越来越值钱,学嵌入式的人也越来越多。面对一个如此朝阳的行业,我们如何才能让自己成为一名优秀的嵌入式开发工程师?嵌入式入门
    发表于 12-17 06:46

    FPGA工程师需要具备哪些技能?

    ,需要具备一系列的技能,才能胜任日益复杂的设计工作。因此,本文将从设计思路、硬件语言、EDA工具、数字信号处理、通信协议、测试验证等多个方面,探讨FPGA工程师需要具备哪些技能。 一
    发表于 11-09 11:03

    工程师故事:怎样才能成为优秀研发工程师呢?

    工程师故事:怎样才能成为优秀研发工程师呢?其实三言两语很难道尽工程师的成长心得。多年的工作中,我们体会比较深刻的有以下几个方面:
    发表于 03-22 16:17 4177次阅读

    工程师在电路设计中的八大误区

    工程师在电路设计中的八大误区,感兴趣的小伙伴们可以瞧一瞧。
    发表于 05-12 10:28 0次下载

    什么样的工程师才能被称为资深Java工程师

    什么样的工程师才能被称为资深,又如何成为资深Java工程师呢?
    的头像 发表于 08-05 16:15 3748次阅读

    如何才能避免】的这七个误区

    当使用参数调用时,会将参数替换为主体,并与其他输入文件一起检查结果,以进行更多的调用,可以将部分来自主体和部分自变量的调用组合在一
    的头像 发表于 12-24 14:29 450次阅读

    电子工程师设计电路的十四误区资料下载

    电子发烧友网为你提供电子工程师设计电路的十四误区资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-01 08:50 22次下载
    电子<b class='flag-5'>工程师</b>设计电路的十四<b class='flag-5'>误区</b>资料下载

    一位工程师总结的电路设计八大误区点资料下载

    电子发烧友网为你提供一位工程师总结的电路设计八大误区点资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-21 08:52 9次下载
    一位<b class='flag-5'>工程师</b>总结的电路设计八大<b class='flag-5'>误区</b>点资料下载