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

三种函数宏的封装方式

[复制链接]
gaosmile 发布时间:2020-12-19 20:32
1. 函数宏介绍
函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。
函数宏本质上为宏,可以直接进行定义,例如:
#define INT_SWAP(a,b) \
! a* h8 N# F$ v; t& }" m    int tmp = a;    \
- d3 v  r8 R3 x% M0 P$ ]1 H' V# h& R$ Z    a = b;          \
5 @) s" z3 [+ e" U    b = tmp0 m' ]$ t/ O6 O! [; C
但上述的宏具有一个明显的缺点:当遇到 if、while 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b 和 b = tmp 均不受控制语句所作用。
因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}、do{...}while(0) 和 ({})。下文将一一对三种方式进行分析,比较各自的优劣点。
2. {} 方式
INT_SWAP 宏使用 {} 封装后形态如下:
#define INT_SWAP(a,b)\! ]0 {& o8 H1 e! |* n. u' ]7 o( T- ?
{                   \
( B5 f$ S# a3 w$ f, S    int tmp = a;    \
/ o6 ?% I. O: }4 d" X! h    a = b;          \, @) F$ S# j* l, [( I
    b = tmp;        \5 s) O' P* z: I: f6 K; m
}/ \" u6 h8 ]) J) [
此时,直接调用与在无花括号的控制语句(如 if、while)中调用均能正常运行,例如:
#define INT_SWAP(a,b) \, V1 x6 F# M9 l( A# @
{                   \6 a2 w& Z/ Q, s: ^! L
    int tmp = a;    \
8 B) R1 ~" f1 l4 p7 n' @    a = b;          \
3 n! u! L7 i2 p+ r% ?7 a    b = tmp;        \
& E; K. B  ~6 ?: B: M! ^& K  }  }+ x7 n}% G# I' o, l/ L/ u6 F
" o# `- _: ?9 @+ |' N  ^+ ]$ R
int main()0 q7 M( \3 g, o0 E: [" u) J
{
5 Y, Y( Y  D( a2 C int var_a = 1;
( u$ Y8 K) ^. m- N- h" O int var_b = 2;- b1 t  X6 ?# E

+ G8 Y" y6 m! X1 t$ \ INT_SWAP(var_a, var_b);6 \+ m' b1 H) E3 i4 `
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1
) e# z9 G& W: Q% v
+ _6 ^0 f8 i, n. G- q; w if (1)
" `  `! R# @# ]- |+ V    INT_SWAP(var_a, var_b);8 t, c- [! B# |" c3 r0 F9 X
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2! G/ ?- Q* p1 `- o
}4 @# A0 q8 c, W1 f0 ]
但当无花括号的 if 语句存在其他分支(else if、else 等)如:
if (1)8 O$ X0 T5 h! k' C4 [* d/ u" ?
   INT_SWAP(var_a, var_b);
% R' N- ~* \3 z1 n* Jelse7 A% W# w) J/ ^$ d; {, N- F+ Q0 k" [
printf("hello world!\n");
3 G8 k( x; o: ]# ]/ Q; z
会发现编译出错:
...
# U7 j8 `! n: C$ E$ B# C/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
2 B. f( t; O/ C; c1 |) H2 Q  a/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’2 N: Q9 Z* M  W
  else
0 T' C& C5 ^7 f
这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。
因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如:
/* port.1 */
) q+ M: i! u1 T3 W# F! Sif (1)3 r, Q1 K0 q' f3 G* e0 P+ s- e1 X
   INT_SWAP(var_a, var_b)
. f! U% p% a, F- b( selse
; J+ d" ]. I4 d8 S9 W0 l) P; K% {# U- H{
9 v3 g& [, o* ?; v% j. m    printf("hello world!\n");
# ^4 w2 y9 C2 a! S% G4 }4 y) ]& M}
- x5 _* s- Q4 G0 A- P% q5 X, a1 i- n0 L* V" A4 T
/* port.2 */
/ o& N, o; g- x% ^3 T- q8 bif (1)
5 Z! A. d  H* ~  t0 s{% [- ?1 r1 q# t0 O' U8 n
   INT_SWAP(var_a, var_b);0 L- @& j# C: s* ]% X9 {
}' y  X# z2 a& z+ i/ D8 M7 s
else
. v9 b+ _( N3 `, t  V; e, H- u{
2 n9 o7 L9 N% F1 S    printf("hello world!\n");& T0 k/ K1 t( x0 I/ r
}
8 @; l$ m( i* Q9 f7 x1 k$ ^" B
可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。
优缺点总结:
  • 优点:简单粗暴。
  • 缺点:不能在无花括号且有分支的 if 语句中直接调用;能够不带 ; 直接调用。
    # y8 g0 \1 T" a& O
3. do{...}while(0) 方式
INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:
#define INT_SWAP(a,b)   \6 |* _& z+ l& G  x$ M1 q1 K! q" e
do{                     \
5 n# I2 V* Q% t; @# ]    int tmp = a;        \  @$ u9 Y& n7 h. W5 o0 W# Q
    a = b;              \1 c5 G- U. f0 a. D% g) A
    b = tmp;            \3 ]$ s: A) |! s& R% }
}while(0)
4 B, }, b2 K5 K/ l! u: b' w, a9 J
do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。
由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:
#define INT_SWAP(a,b)  \
; D, d+ I; f' U* N% u+ j* C. gdo{                 \
3 y: t. a# k, e; J' U if (a < 0 || b < 0) \
4 ^/ {: P& g, }" \, Q; D2 l  break;   \) V/ ?, ~: d/ R4 A1 C, {  e
    int tmp = a;     \
$ @% u  m7 b+ `# R& K    a = b;           \
# z, N8 ^% q: v* D1 e. N$ B# K    b = tmp;         \6 }$ i7 w' m4 z- c5 X' W( t
}while(0)3 z# X/ n1 h1 z6 z+ z; ^0 t
由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。
因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \
2 U1 x: Z9 m! Q9 N# I( X3 q9 q3 Cdo{                 \
* Q1 r0 }% P. k1 k" E1 q; C if (a < 0 || b < 0) \
- s1 L- [; `9 ^, R3 P& H% ]8 d1 B  break;   \/ f  D* G2 }2 y6 ?: d
    int tmp = a;     \% Y" t- ?  Y  v6 r
    a = b;           \
4 F; \! v# Y+ p7 Y0 P    b = tmp;         \
8 _  I  I+ ]1 U; V  E# R  ]: }  f}while(0)
% U& ^  P0 u# w' m
4 U4 X8 U- K  k, H" K5 e! D7 kint main()
: E+ o5 |" ?- q& l5 e{
  ~5 w; Q, G! p8 o# `' J int var_a = 1;* M! G  r7 X. c' H4 V
int var_b = 2;
+ B% Q9 g6 U) L4 l. `: {6 N
! Q. h* N+ ]! W5 |7 E$ H' O: s if (1)
' }+ q' n  P3 P; q  [6 E" N# q    INT_SWAP(var_a, var_b);, V' m0 w- l/ `5 o0 Q0 W
else
/ ~' U% b  y) c) m9 Z8 e  printf("hello world!\n");
6 z! \7 Q! r8 x% i) `* \/ W printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 13 D3 v7 c0 R( v  B6 P
# I' V( E9 B, y6 ^% @& b+ M2 f
return 0;
7 A3 ?$ U2 \, G% q' k( ]$ w% c}1 ?4 \9 Z: P5 Y4 j' {& a4 E8 Q9 I( j
; B- A7 I! d! A3 n' P$ U) l4 N/ V2 M
C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:
if (1)- Q7 R% L) l! Q- A
   INT_SWAP(var_a, var_b)
7 F9 z" u0 C3 U5 [5 t; ^else& p# p3 f: A1 a" `  H
{2 X6 D& \7 I' Y
printf("hello world!\n"); 8 h0 }! J/ ~6 J; [% p
}
  z/ |3 T+ H6 G& d2 ^8 H- S, V6 @& F
优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;支持提前退出函数宏;强制调用时必须使用 ;。
  • 缺点:无返回值,不能作为表达式的右值使用。$ @5 x' T& S! P+ W, T- b" m. o! j
4. ({}) 方式
({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。
INT_SWAP 宏使用 ({}) 封装后形态如下:
#define INT_SWAP(a,b)   \( ]4 @+ X7 v5 ?& e* S& E2 e
({                      \
" V$ v3 A9 \8 M8 h/ a4 W, |    int tmp = a;        \
  v6 J( {; K1 ^. p1 y) E# Z' r1 Q    a = b;              \
, T: ]: w0 n, L2 U0 X    b = tmp;            \
" q& @+ D) F# c3 s% ]" T: g) |})
, ~+ S, V. g; }  \& R4 l
2 S8 U* g4 h3 o( z% x& x
与 do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \7 V. [4 O( w; D7 d0 s; P/ @
({                 \* e, p% N( C# {, u! n' x, r' Z8 {
int tmp = a;    \  b* q1 j6 i9 N3 F( w$ j5 V1 p
a = b;          \
! A5 Y) g2 ]. n b = tmp;        \
. @4 C  U6 O0 ]3 z5 M}). u! `0 g1 `0 {. |/ f1 }; P3 F6 I; c

$ U% [* _5 c7 _int main()
' c' f9 S4 J: y{
# V( N/ C! \& w  M7 k, M int var_a = 1;) U+ j" v8 {' N
int var_b = 2;3 B4 n8 r( {8 h2 i0 O

7 r6 O' R: J% |, L4 a5 d if (1)8 N9 g2 H* p6 M& L: w& A7 i& r
    INT_SWAP(var_a, var_b);
# C7 G- X) ~$ B. N else" M: M1 a) l, r8 k( q
  printf("hello world!\n");
% k1 ?; f: i4 H8 W. q& m  L# K6 A  t printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
0 f9 }. Y! W; u  G" h  M4 D& H
0 v; r& ?: d* x& u; X( @ return 0;
- y$ B2 m& z( J$ \% ^% I) i}
0 s$ P4 y* A  [7 ]( A" Y1 A. r5 A3 Z' t6 e
与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?
答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:
int main()
; `! ~6 n5 ~  V- l& q! {& N9 W{
8 x* |( s# K( o int a = ({
6 _0 B/ o8 k$ f0 m  10;
8 X8 G5 M4 _6 }7 j1 C) a$ F5 \  1000;2 ]" A5 F7 D( s" I
});
' _. t  ~* B# |9 Y1 _5 U printf("a = %d\n", a);      // a = 1000- J% H9 {, n9 Q2 \+ X
}0 }3 m& q+ v  f
; M+ ]3 G$ Q4 L$ Q- ^8 g0 {# w; N1 @
因此,({}) 可以为函数宏提供返回值。例如:
#define INT_SWAP(a,b)  \/ v+ Y- g3 J: {. L
({                 \$ N& z! {4 @$ c  q) ~+ E
int ret = 0;  \' q3 f( k5 ]+ V: q' N: d
if (a < 0 || b < 0) \0 l7 r+ i- ^: F* l% R3 ?1 e6 i2 [. s
{     \% Y' y/ o9 _* _# v2 q/ N
  ret = -1;  \
' x5 l+ Q" c1 [0 u* k }     \" s9 m+ ?( }4 e3 h+ {! N! E5 M
else    \
4 g- l* }8 W# G; N9 h- i0 Y {     \  C. D; Z) ?% H
  int tmp = a;    \# n: J4 \) }. b; d3 I4 v
  a = b;          \2 a* }2 A& T7 h' Z1 l0 B! t. Z
  b = tmp;        \/ b5 S( K9 D- H: Z8 d
}     \, f* w# W1 `* V/ J. T
ret;    \0 {  z$ b+ d: T; y- Y% x) P
})- q$ ^: @: M" H3 o2 ~8 @

: a( @6 N  F8 [6 D. i1 r0 oint main(). |. N2 y2 f( r8 D. t
{
9 ?! m) T) @1 K" Q/ E; o6 \( v int var_a = 1;
8 I! U/ P9 P5 @) u6 d6 p. S int var_b = 2;+ P# j) l; Y. ~6 h! {: s: {* ^
6 A: ^! r! I+ O6 h  h. f8 |
if (INT_SWAP(var_a, var_b) != -1)) i0 i" _' M4 U) N- k. N1 W5 ]
  printf("swap success !!\n");     // swap success !!! P, k& f. J4 s
else
' z+ ]" J3 X3 ~. k$ a3 F  printf("swap fail !!\n");
- i0 B0 B+ |" E& H printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
% L2 S! r# Y; [
5 y9 r# U7 b& u5 n& h return 0;% M! }) \! F5 n2 v2 g
}& i- h: M1 T; ?: x3 o7 ?6 T
可见,此时的 INT_SWAP 宏已与函数十分接近。
优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;有返回值,支持作为表达式的右值。
  • 缺点:不支持提前退出函数宏;非 C 的原生语法,编译器可能不支持。
    6 F5 o: ^" `! `% X7 w5 J1 Z
5. 总结
综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。
5 @$ V+ u+ E* }/ O* M0 B* t- H
收藏 评论0 发布时间:2020-12-19 20:32

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版