EDA365电子论坛网

标题: 函数宏的三种封装方式 [打印本页]

作者: monsterskyy    时间: 2020-12-18 13:36
标题: 函数宏的三种封装方式
1. 函数宏介绍
函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。
函数宏本质上为宏,可以直接进行定义,例如:
#define INT_SWAP(a,b) \! m% C, B% Q; t+ ^0 S
    int tmp = a;    \
9 L! \! _- P& |7 U    a = b;          \
+ e- Y$ V: K5 l/ ]    b = tmp
9 n4 P% C% s7 E! P! Y. u
但上述的宏具有一个明显的缺点:当遇到 ifwhile 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = bb = tmp 均不受控制语句所作用。
因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}do{...}while(0)({})。下文将一一对三种方式进行分析,比较各自的优劣点。
2. {} 方式
INT_SWAP 宏使用 {} 封装后形态如下:
#define INT_SWAP(a,b)\
( r. O# E/ [( l$ v7 R0 M% ~( D{                   \
$ B' W9 d8 N7 L' x2 z' [1 b    int tmp = a;    \
. F8 A: n2 Q5 b% X4 l7 L% r' e( p9 R4 @    a = b;          \
* D: d* M+ L1 c8 G    b = tmp;        \$ J9 {$ u; T( q* e3 V8 O' E% R
}

" J7 I( |1 @1 D6 b4 R, m
此时,直接调用与在无花括号的控制语句(如 ifwhile)中调用均能正常运行,例如:
#define INT_SWAP(a,b) \
' y0 P* A" G2 _; C{                   \" `% U* [+ ?! r0 H% A& ^
    int tmp = a;    \  O' Z' Y# b; g. t. d4 c8 D# b
    a = b;          \9 a) U" v8 a2 l
    b = tmp;        \
6 N) m( I5 d/ B}
) E* p" ?6 ^+ p( t

3 `6 y1 D" z  Z! j5 y: Oint main()
7 P' h. [/ \* X  f" ]" R2 D{
3 J0 a+ X2 E3 N8 I6 c int var_a = 1;; A; T/ d% z; Y
int var_b = 2;
6 Q0 v$ h, S$ s; U7 v! S* |% {
- m' U; k% E- r9 w INT_SWAP(var_a, var_b);2 r0 @* `: u3 F* w7 q: P+ a% k
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1
0 U8 j6 g- @" \, ~  C2 ]; n 3 I- g0 o9 o& r% Y2 V! c! F
if (1)
  T1 u% O* Y& u# g8 [" a    INT_SWAP(var_a, var_b);6 m5 ?0 V8 e3 j3 L
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2
, j3 E$ T! \+ Q5 P; Y}9 y5 g$ l  I  g6 Z" z) M. Q1 [* M7 L
但当无花括号的 if 语句存在其他分支(else ifelse 等)如:
if (1)
2 B" I6 T  j1 z4 l  }/ f   INT_SWAP(var_a, var_b);# ?/ [, B  E( S6 D, ?1 P' H
else
; Q; N1 R) L$ Q$ k8 a0 w+ I printf("hello world!\n");
9 {6 r9 N- C. ^' W
会发现编译出错:
...
8 x( f7 [/ I. y0 Y9 a/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
$ y4 j. ]% v# l. u- Q) Q2 W/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’" T0 d3 U2 r3 c: K$ F/ E
  else9 B# F) R5 y  q9 l
这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。
因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如:
/* port.1 */
. b- ^/ [* b/ k7 s; aif (1)
8 f6 v, N( \7 z2 t+ z   INT_SWAP(var_a, var_b)$ g9 u/ h3 R' t
else8 h. t" e8 E: k" g2 a9 H& y
{. p8 `, b. b5 a' H
    printf("hello world!\n");
- T/ f2 U% ?( @$ _" ?, O3 Y7 i}
; o' ]3 Z  }, q- u
9 Q# @2 f. ^# R( [/* port.2 */
5 ]* k3 o% s' C8 Qif (1)
$ d$ ]  s* U5 O. l2 }0 U3 _{  o( Y+ k8 a1 U. f
   INT_SWAP(var_a, var_b);
  r. W  N$ n# B/ l$ u}! K& ]9 m( j. ^1 n3 @3 g: D# F
else
( y) x: \3 c. ~$ u  B  e, j{  {; r5 }7 e- A9 n
    printf("hello world!\n");+ J# w5 `; S" I3 X2 K  r: ]' \; T
}
/ U' l4 r# G9 P# b: y& k
可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。
优缺点总结:
3. do{...}while(0) 方式
INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:
#define INT_SWAP(a,b)   \6 a" P5 t" d8 \' s9 s  V5 [' j& E8 Z
do{                     \3 ]- c0 l$ e$ M: n, J! f
    int tmp = a;        \( Y% G3 b3 [" q
    a = b;              \( `) y0 g& m6 y
    b = tmp;            \. j: j- T+ X- ^
}while(0)
# c3 S# u! F" ^
do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;
由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:
#define INT_SWAP(a,b)  \
( n2 |4 I2 @' C$ s$ }do{                 \2 ?: K+ ~7 e9 G8 u# v7 Q: N
if (a < 0 || b < 0) \  {7 K5 T( V8 d( w  c$ w$ Z, O
  break;   \, c/ Z9 F! A' A- k' }$ T7 f) _+ q
    int tmp = a;     \5 |. s$ F( K+ K% X# k; b
    a = b;           \
: {, b% j& V( E  ?    b = tmp;         \2 v4 Z/ x/ |9 a. T2 l! B, F6 c" @+ I
}while(0)

& x) K3 D" k! c8 v. p% P4 t
由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。
因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \0 p6 R8 S6 ~4 l6 i8 v9 I4 |8 @
do{                 \* h) U( p  k6 g6 S4 a
if (a < 0 || b < 0) \' w' s) a( {; f' u9 s) a
  break;   \% a; W, F3 [7 O4 d$ T; v3 A. s
    int tmp = a;     \2 u: x4 p* Q1 Q/ B
    a = b;           \( K* N2 S. p3 T! T1 a- X
    b = tmp;         \
* Y  N4 c$ G* o9 m1 G) ]}while(0)

5 D% ~7 C' C  j  K3 I9 A3 s# @# V( `3 Q- M) h4 e$ @# q1 T
int main()
, G. M; w0 j' v1 K! F+ T+ i+ q4 ~{
9 p0 J1 a4 _! m4 s' T# i' b int var_a = 1;, B5 j, b9 q  r  L8 E# c
int var_b = 2;
, Z# E6 x+ R/ z: ^) W1 }; G9 R. j  I% m: O) L, N$ R
if (1)
  ]; D& Q' e1 h    INT_SWAP(var_a, var_b);# f  G/ R* u4 p# D
else+ D6 M$ I0 k% U3 Q) u6 a
  printf("hello world!\n");
3 m- V1 i+ B2 P. r; w printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
% M7 r: E2 B( ^, y+ W* k4 k( O7 J" `, x- {- k
return 0;
6 L& L% W+ n5 y$ z}
0 c1 J& d2 _. C  @  H. i% X+ O7 H" T* z+ k
C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:
if (1)3 S- f  }' k8 g% w
   INT_SWAP(var_a, var_b)5 F) c6 B$ x% X7 W2 _' `  t1 |1 l
else
( C( b* c5 S  h{& |) \5 s4 ^  _$ j+ j- h; I
printf("hello world!\n");
3 w' V( V9 \/ b! f) [$ F}+ P7 ~* x3 U* j  Y

( `8 B; P( s2 H* s0 l2 _5 p
优缺点总结:
4. ({}) 方式
({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。
INT_SWAP 宏使用 ({}) 封装后形态如下:
#define INT_SWAP(a,b)   \
" e6 L  w, g% T, {$ `( y({                      \
  a4 x  ?# |. @    int tmp = a;        \
# E  u7 Y) \+ A3 P    a = b;              \6 [% q/ P9 T' L& M8 t8 S# K
    b = tmp;            \6 i& Y% h; A$ Z/ X
})
* I5 w  O1 w# }! u+ e1 n

, S+ y6 }8 L9 L; s# m5 P8 ?& Y2 Y
do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \( k8 B2 y1 r% I1 k; [2 C; k( B
({                 \7 g( F  N, w( e) d, L3 T
int tmp = a;    \- ]0 {/ r/ x% w+ Z  k: O
a = b;          \
, m# ?2 j6 ], |, X' O& X2 d# d$ v b = tmp;        \% J! c  u0 x6 T
})

4 N5 ]+ G2 k( f- M& k& @' \$ X9 |4 V  D: z  d9 y' M* _2 q; I. }6 s4 ]* p
int main()
+ o! p' x6 B( ]- E0 p{( N- E0 V! T7 A$ E5 I5 D7 d
int var_a = 1;
" u5 A# c  b& F0 G: H4 i int var_b = 2;
# D4 S' ~. [$ T3 O; B
6 J  h9 k- d$ |- {/ b, v if (1)
! A* B, @3 g4 n" h1 F9 I. w    INT_SWAP(var_a, var_b);
- f0 Z) e5 u1 n/ P8 ~* O1 k else: Y# G& _. W6 q2 @& j  e! E9 L
  printf("hello world!\n");. w" L  b1 }8 `8 f2 W
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
$ j) m7 k+ \7 H
. K5 ?- E; ^( H" I return 0;; y& ]; |- e- d" ^: t* S
}
" W3 G( @3 |. {, h$ s! b# O  i4 K
# X3 @! u0 U" N/ h9 }
do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?
答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:
int main()
! k7 b, \- s: Q2 I{
, ?! q: m) [, K3 `& Y) N; l( z int a = ({, F& C5 G9 T; W3 g$ Y  l8 C2 N
  10;
9 C) a" |) O# ^" R1 Z! a* }  1000;
0 M0 n- H9 E7 ^0 x, {+ Q });2 T1 {$ S- a1 j8 Q
printf("a = %d\n", a);      // a = 1000& t5 `1 v* y3 d7 T0 b8 d. y
}$ x& s9 Z5 g& d) {& @5 m& T) u
0 c" {% v0 {3 i1 t' V- F) V. K
因此,({}) 可以为函数宏提供返回值。例如:
#define INT_SWAP(a,b)  \. {' j; V* }; a4 c( \) H
({                 \: d! ^. o2 C4 x+ w6 A  e
int ret = 0;  \
- V% Z5 H9 X4 c" b/ @ if (a < 0 || b < 0) \/ _( a) f9 C# F
{     \
0 V, A$ J( o8 P0 r; i# |9 M  ret = -1;  \  @7 r5 n+ m2 O3 Q6 N  w
}     \' f5 d0 ^7 k9 w6 F1 _: Y
else    \! q2 N4 H: A( H7 n( U: f
{     \  y! l' v& [0 J
  int tmp = a;    \
) _9 z. ]; c4 |) h' g9 o8 |  a = b;          \
; [+ f; V1 o/ G+ ?1 V" c* C) T  b = tmp;        \/ ]" A2 S8 j8 D! t* l! t
}     \
3 z& Q2 n# o# O9 k2 y ret;    \; i/ x4 Q' c' n
})

7 R! C3 [0 T% k1 ]7 m1 p+ `9 B8 Y7 ~# R
int main()
$ |1 {* B* b1 x: v( ~{/ t- Z, ^8 a- V! x/ @6 o7 o9 n
int var_a = 1;
: J9 ]1 c; {- f1 ]8 p- q! h int var_b = 2;4 X% R3 H9 ]% |- Q
4 {, [, Z1 \) z
if (INT_SWAP(var_a, var_b) != -1)
/ t4 W1 {3 E0 H3 a" _6 _  printf("swap success !!\n");     // swap success !!4 P5 _" c+ S. f. L7 }
else
) K/ c6 I9 P. e+ w  printf("swap fail !!\n");
( a# F. D) o! _8 F7 X# y printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1; B9 h3 m* H6 P

( p7 z* c( ^. s6 ^" i/ O return 0;
) w/ Z; {' n/ Z( \- D}2 x- t0 Q+ B. B9 F4 \  c
可见,此时的 INT_SWAP 宏已与函数十分接近。
优缺点总结:
5. 总结
综上,在 {}do{...}while(0)({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。

) d( Y: X+ l" C5 m' o; N
作者: modengxian111    时间: 2020-12-18 14:44
函数宏,即包含多条语句的宏定义




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2