你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【宏】的7个误区

[复制链接]
gaosmile 发布时间:2020-10-16 20:47
语法错误
当使用参数调用宏时,会将参数替换为宏主体,并与其他输入文件一起检查结果,以进行更多的宏调用,可以将部分来自宏主体和部分自变量的宏调用组合在一起。例如,
#define twice(x) (2*(x))
2 z0 ~: T" N+ H$ l* {9 @#define call_with_1(x) x(1)
: _+ O0 M0 I$ d' N& X' r: p' Scall_with_1 (twice)
2 C, m" ^0 e. r7 F6 A" N$ ^& H
" J+ R; d9 E% v3 }//x=1
1 ?, f; G0 G+ v) C3 f; d3 K → twice(1)" j3 W3 K5 ~: ?" M! O+ {: M3 w  P
→ (2*(1))5 F7 _% [" L9 q9 d
宏定义不必带有括号,通过在宏主体中编写不平衡的开放括号,可以创建一个从宏主体内部开始但在宏主体外部结束的宏调用。例如,
#define strange(file) fprintf (file, "%s %d",4 d' K$ h* ?% w
…
& s7 N# e- Z* P  [: G: mstrange(stderr) p, 35)
) ?& x) Y0 ?  I+ }( Q     → fprintf (stderr, "%s %d", p, 35)
7 }$ U; W9 b1 H. l* G" a7 T! n
组合宏调用的功能可能会很有用,但是在宏主体中使用不平衡的开放括号只会造成混淆,应该避免。
运算符优先级问题
在大多数宏定义示例中,每次出现的宏参数名称都带有括号,并且另一对括号通常会包围整个宏定义,这是编写宏最好的方式。举个例子
#define ceil_div(x, y) (x + y - 1) / y
9 E9 u2 V9 p9 H: [/ x8 A
假定其用法如下:
a = ceil_div(b&c,sizeof(int));
6 K3 F! z( y( R& E5 m
拓展开是
a =(b&c + sizeof(int)-1)/ sizeof(int);/ M5 G9 y( b4 _
这没有达到我们的预期,C的运算符优先级规则使其等效于此,而我们想要的是:
a =(((b&c)+ sizeof(int)-1))/ sizeof(int);
5 I9 P3 k- g1 X8 h
如果我们将宏定义为
#define ceil_div(x,y)((x)+(y)-1)/(y)
. l4 O) e& ^  @, C: G0 D; _, e
可能导致另一种情况,sizeof ceil_div(1,2)是一个C表达式,可以计算ceil_div(1,2)类型的大小,它扩展为:
sizeof((1)+(2)-1)/(2)
+ a$ {3 Z# J* c* g3 g' ]$ ~3 D. r
这将采用整数的大小并将其除以2,而除法包含在内部的sizeof之外。所以整个宏定义的括号可防止此类问题。那么,下面是定义ceil_div的正确方法如下
#define ceil_div(x,y)((((x)+(y)-1)/(y))
6 i/ S7 u$ n0 N% }( C
吞噬分号
通常需要定义一个扩展为复合语句的宏。例如,考虑以下宏,该宏跨空格字符前进一个指针(参数p表示在何处查找):
#define SKIP_SPACES(p, limit)  \; e# j( Y9 C$ ]2 |6 t  g
{ char *lim = (limit);         \
8 s/ n! \5 x6 c  while (p < lim) {            \$ i+ R8 D2 s9 Q# ^, D. N' ~9 o
    if (*p++ != ' ') {         \+ ?: x: g, d4 ^4 S6 z
      p--; break; }}}
3 ?7 T" d# F1 M8 f; p( [9 N
该宏定义必须是单个逻辑行,严格来说,该调用扩展为复合语句,这是一个完整的语句,不需要用分号结束。
但是,由于它看起来像函数调用,因此,如果可以像使用函数调用一样使用它,则可以最大程度地减少混乱,然后再写一个分号,就像在SKIP_SPACES(p,lim)中一样。
这可能会在else语句之前出问题,因为分号实际上是空语句。假设你写
if (*p != 0)  s& o/ S) L% w  w" j3 M( \' J5 K
  SKIP_SPACES (p, lim);/ ?9 e, {5 K( r. J) Y1 ?
else …; F: B# T# \+ L5 o' M
在if条件和else条件之间存在两个语句(复合语句和null语句)使C代码无效。
怎么解决?我们可以使用do…while语句更改宏SKIP_SPACES的定义以解决此问题。方法如下:
#define SKIP_SPACES(p, limit)     \
" q/ [3 j; E. Q) Rdo { char *lim = (limit);         \
! ^4 x  s5 |: N' v. Z2 e     while (p < lim) {            \3 c' B0 @5 ?5 q7 s  h
       if (*p++ != ' ') {         \
0 }7 ~. o4 @7 f9 ?! f; O" f! b         p--; break; }}}          \' Z" B) m, U/ H, c& x& j. J
while (0)0 f" u0 h& r) i
SKIP_SPACES (p, lim);扩展为
do {…} while (0);0 Z/ s/ @/ g1 E
这是一个陈述,循环仅执行一次,而且大多数编译器不会为此生成任何额外的代码。
重复调用
我们常见的“最小”定义一个宏min,如下所示:
#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
  Y; t: ?1 F* i& q& D  Z6 Q/ q9 I- K
当将此宏与包含副作用的参数一起使用时,如此处所示,
next = min(x + y,foo(z));
5 s+ r$ `8 s0 M+ R* u, D$ U3 s/ V0 f6 ^0 w5 ^/ z* S9 J" v- k
它扩展如下:
next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));
6 J+ ~3 I6 A, _; d
6 U3 m" D6 e$ H% S9 e
其中x + y替换了X,而foo(z)替换了Y。
函数foo出现在程序中的语句中仅使用一次,但是表达式foo(z)已两次替换到宏扩展中。结果,执行该语句时可能会两次调用foo,所以min是一个不安全的宏。
解决此问题的最佳方法是以仅计算一次foo(z)值的方式定义min。C语言没有提供执行此操作的标准方法,但是可以使用GNU扩展来完成此操作,如下所示:
#define min(X, Y)                \" p; Y% O; \  k8 N
({ typeof (X) x_ = (X);          \# x9 ?8 |" h# }! M5 [
   typeof (Y) y_ = (Y);          \
7 N: w. [* N+ N   (x_ < y_) ? x_ : y_; })5 W" }$ Z7 N2 U, c* V
“({{…})”符号产生一个复合表达式,它的值是其最后一条语句的值。
如果不使用GNU C扩展,唯一的解决方案是在使用宏min时要小心。例如计算foo(z)的值时,将其保存在变量中,然后在min中使用该变量:
//假设foo返回int类型
' ]; |8 h  k) G2 d#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
2 l1 J7 J1 K9 \6 a3 K9 c9 b…, z: X! A3 G! S& K' ?
{: q& P- E! z; A: O& B
  int tem = foo (z);
; f% [: j% e, T0 Y' f4 A! S  next = min (x + y, tem);9 P8 o/ F( C; D! Y5 S' I
}+ l# c: I( ]  B. T0 J$ G4 E
自引用宏
自引用宏是其名称出现在其定义中的宏。我们知道所有宏定义都将被重新扫描以查找更多要替换的宏,如果自引用被认为是宏的使用,它将产生无限大的扩展。
为防止这种情况,自引用不被视为宏调用。它原样传递到预处理器输出中。举个例子
#define foo (4 + foo)7 t: |6 ^8 P4 B' T6 _( O. P2 g- z/ Z$ x

0 o) e4 O5 A6 g# n( x2 ]
按照普通规则,其宏定义分析如下
  • 对foo的每个引用都将扩展为(4 + foo);
  • 然后将对其进行重新扫描,并将其扩展为(4 +(4 + foo));
  • 以此类推,直到计算机内存耗尽。
    % b' m' v' p0 i
自引用规则将这一过程缩短了一步,即(4 + foo),因此此宏定义可能会导致程序在引用foo的任何地方将foo的值加4。
阅读程序的人看到foo是变量,就难以记得它也是宏,真的会坑爹的。它的一种常见有用用法是创建一个可扩展为其自身的宏。如果你写
#define EPERM EPERM
; p" A" g1 n+ o+ j4 f" {: c
5 W1 a- E2 I. _/ R1 `' p# h
然后宏EPERM扩展为EPERM。实际上,每当在运行文本中使用预处理器时,预处理器都会将其单独保留。
如果宏x扩展为使用宏y,而y的扩展引用了宏x,则这是x的间接自引用。在这种情况下,x也不展开,举个例子
#define x (4 + y)
# _6 g& G; J% |/ v2 U#define y (2 * x)
0 n( I% b- _& `, x4 Q$ B6 p
然后x和y扩展如下:
x→(4 + y)
; d! Y7 s! W2 i     →(4 +(2 * x))
9 U& s' g, K$ F3 P2 q7 g' M/ M) F' Z3 V5 H* q* {8 I* @
y→(2 * x)- s% H7 G3 L: B; I7 b! T1 v
     →(2 *(4 + y))9 t0 d5 _+ @" d9 {
当每个宏出现在另一个宏的定义中时,它们将被展开,但是当它间接出现在其自己的定义中时,则不会被展开。
参数预扫描处理
宏参数在被替换为宏主体之前必须经过完全宏扩展,替换后,将再次扫描整个宏主体,包括替换的参数,以查找要扩展的宏。
如果参数包含任何宏调用,则它们将在第一次扫描时扩展,那么结果不包含任何宏调用,因此第二次扫描不会更改它。
如果按照给定的方式替换了参数,并且没有进行预扫描,则剩余的单个扫描将找到相同的宏调用并产生相同的结果。
预扫描处理在以下三种特殊情况下有大的作用。
对宏的嵌套调用
当宏的参数包含对该宏的调用时,就会发生对宏的嵌套调用,举个例子。
如果f是期望一个参数的宏,则f(f(1))是对f的嵌套调用对。通过扩展f(1)并将其代入f的定义来进行所需的扩展。预扫描会导致发生预期的结果。
如果没有预扫描,f(1)本身将被替换为参数,并且f的内部使用将在主扫描期间作为间接自引用出现,并且不会扩展。
调用其他可进行字符串化或连接的宏的宏
如果参数是字符串化或串联的,则不会进行预扫描。
如果要扩展宏,然后对其扩展进行字符串化或串联,则可以通过使一个宏调用进行该字符串化或串联的另一宏来实现。举个例子
#define AFTERX(x) X_ ## x4 s# Q  f: l6 ~
#define XAFTERX(x) AFTERX(x)
1 s9 ~1 H+ D& o#define TABLESIZE 10247 j0 W7 Y% P$ U
#define BUFSIZE TABLESIZE3 G0 r3 y9 E: ~# A; B0 H$ m; ~! V
然后AFTERX(BUFSIZE)扩展为X_BUFSIZE,而XAFTERX(BUFSIZE)扩展为X_1024而不是X_TABLESIZE,预扫描始终会进行完整的扩展。
参数中使用的宏,其扩展名包含未屏蔽的逗号。
这可能导致使用错误数量的参数调用在第二次扫描时扩展的宏。举个例子
#define foo  a,b% R& i7 [; Z; M8 d+ s) m/ J4 h
#define bar(x) lose(x): `; [- H) U8 j0 U4 C6 H9 h8 Q
#define lose(x) (1 + (x))
" _  Y/ Q. d7 [- t
我们预期的结果是bar(foo)变成(1 +(foo)),然后变成(1 +(a,b))。
然而bar(foo)扩展为loss(a,b)会出错,因为Los需要一个参数。在这种情况下,该问题可以通过使用相同的括号轻松解决,该括号应用于防止算术运算的错误嵌套:
#define foo (a,b)7 b! ~) ^5 `' J5 d0 G& z
or
8 U1 a/ |' c' @" m) Z7 \6 A#define bar(x) lose((x))
2 J& Y. n4 A& [
多余的一对括号可防止foo定义中的逗号被解释为参数分隔符。
参数中的换行符
类似函数的宏的调用可以扩展到许多逻辑行,但是在本实施方式中,整个扩展是一行完成的。
因此,由编译器或调试器发出的行号是指调用在其上开始的行,这可能与包含导致问题的参数的行不同,例如:
#define ignore_second_arg(a,b,c) a; c7 {% ]* x+ u! t4 a5 q; A" w7 }: W

2 j& l3 x; j( i6 U2 q4 _' aignore_second_arg (foo (),( T7 z- R' n$ B. l" v  E% {9 `
                   ignored (),
9 n6 o# ?% k( w                   syntax error);. J( d2 x7 _9 g/ }
由Syntax error on tokens触发的语法错误会导致错误消息引用第三行(ignore_second_arg行),即使有问题的代码来自第五行。

* E  r* F; u* E5 e
赞 收藏 评论0 发布时间:2020-10-16 20:47

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版