找回密码
 注册
关于网站域名变更的通知
查看: 346|回复: 1
打印 上一主题 下一主题

函数宏的三种封装方式

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-12-18 13:36 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
1. 函数宏介绍
函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。
函数宏本质上为宏,可以直接进行定义,例如:
#define INT_SWAP(a,b) \" U2 L0 Y1 q7 O( Q0 ^! Z/ {9 J
    int tmp = a;    \- j1 O3 ?1 s' J! k
    a = b;          \
* f8 L. o/ R4 I- L  N5 W+ z  j    b = tmp

! b( D' D7 ?$ j9 B* ^
但上述的宏具有一个明显的缺点:当遇到 ifwhile 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = bb = tmp 均不受控制语句所作用。
因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}do{...}while(0)({})。下文将一一对三种方式进行分析,比较各自的优劣点。
2. {} 方式
INT_SWAP 宏使用 {} 封装后形态如下:
#define INT_SWAP(a,b)\# r: [  ~3 u0 \6 c5 b9 s8 i
{                   \" g3 k: _$ ?9 i7 ]# k7 g
    int tmp = a;    \
1 \8 t9 [! a/ Q. t' \% R4 J1 P0 r    a = b;          \
  M* e6 k; E- _+ X    b = tmp;        \
; q0 W2 U& R) ~8 O; z}

4 o  Y0 p4 B) Y# X
此时,直接调用与在无花括号的控制语句(如 ifwhile)中调用均能正常运行,例如:
#define INT_SWAP(a,b) \
" D; f$ U* M0 Q# G! j7 q{                   \
% Y" w: f3 ]5 S4 [: F    int tmp = a;    \
" J4 Z, v9 U2 q& {    a = b;          \
9 c0 e4 [2 D: O' E) W  L- p    b = tmp;        \
* z3 @1 |0 ~1 _% w8 O}
% ]( J9 a2 ?: y( Y

/ F" O! E2 u# _3 r0 z3 k( {9 a' ^int main()
: w) c- k8 W( d{" z! w$ \- m' i( j2 s! U
int var_a = 1;! p4 I0 r& m- V$ v' X0 b
int var_b = 2;# @% k) F4 u; ~

! ~1 K, x$ h; u3 ?. X INT_SWAP(var_a, var_b);
7 h8 S" k# s4 W printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1
  y- s, z5 `1 _+ ]8 O) P. O# O; k . X5 G) a, r, m! p7 c  S+ }) M* d
if (1)" C, p: |( _6 N
    INT_SWAP(var_a, var_b);
  p5 y) `1 Q; {3 j) [% W) b2 { printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2
1 ^# z7 I& ]) |}
6 q/ [2 q& I: R% @/ G/ R
但当无花括号的 if 语句存在其他分支(else ifelse 等)如:
if (1)
+ Q; R3 t1 z# ]2 U   INT_SWAP(var_a, var_b);
0 F( V9 P7 w  @else- U+ T" w* @2 Y
printf("hello world!\n");6 W; x2 g( L, p& U0 u
会发现编译出错:
...
; Q: J8 S  s3 \2 j/mnt/hgfs/share/pr_c/src/main.c: In function ‘main’:
# n# r3 |7 v; h8 t, a* ]/mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’
1 H/ z4 ~% e% N3 h# A& ^$ b/ K, W/ E  else; A& ]+ Z) [- p  m' {( ?
这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。
因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如:
/* port.1 */9 k/ e3 l5 o. q8 f: Y& n* f
if (1)
/ l$ t) B0 I* s9 |. t0 g   INT_SWAP(var_a, var_b)$ ]. z2 w6 N* y1 F' A; L- N( l$ ~. t3 k
else" P$ f9 t2 f9 _# W
{! |. `) T+ d2 F/ m* H$ F& m* o
    printf("hello world!\n");
! ^# S. o/ e4 A}# d% B! a4 P- o* `- @2 x7 u
# e2 n/ j! K* \$ ^4 b9 b+ q
/* port.2 */- [' p1 l1 T4 k
if (1)
6 t: T% o; V6 O( i! s2 d- Y{
# d9 [! c+ ~7 \. b& n; ?   INT_SWAP(var_a, var_b);6 F9 \9 }8 _3 y6 Z( d
}
$ E! x( p0 d7 ~8 g+ C* b8 i* Ielse
0 x+ ?5 j# Q7 {" `{9 r7 ?& G* Q  h! K# |
    printf("hello world!\n");
, l% w, D4 Q5 V  @}
7 ~. p( B3 f. [; z0 J
可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。
优缺点总结:
  • 优点:简单粗暴。
  • 缺点:不能在无花括号且有分支的 if 语句中直接调用;能够不带 ; 直接调用。. H" ?. d' w) w' G
3. do{...}while(0) 方式
INT_SWAP 宏使用 do{...}while(0) 封装后形态如下:
#define INT_SWAP(a,b)   \
1 b& [% O, T6 pdo{                     \
) Y( {" Y8 `3 S6 n9 I  ]    int tmp = a;        \; [7 N- u9 W9 q2 ?+ N! k) S& i+ C
    a = b;              \% U( m8 ]! U! @4 ]
    b = tmp;            \
9 I! w# `+ S% j  x  l$ o0 ?}while(0)
  J% Y: g, a6 n9 O7 C
do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;
由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如:
#define INT_SWAP(a,b)  \
2 g* ]- z2 E; vdo{                 \
5 R. c  @7 ~+ A9 A/ i( R if (a < 0 || b < 0) \
5 o% a* K; y  A4 l  break;   \
0 y4 M- f& v1 B# H8 Q    int tmp = a;     \
3 u, s# I% C8 y4 K    a = b;           \( C. n3 N; y4 M' Y) b$ R
    b = tmp;         \
1 i8 m; K# A4 t' n1 X) _6 m}while(0)

" l) D" D# {( Z  S! P
由于 do{...}while(0); 实际为一种语法,编译器会把 do{...}while(0); 认为为一条语句。
因此,do{...}while(0) 方式的函数宏可以在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \
  t0 F' X& t; j0 C) jdo{                 \
; {' v0 {! Y/ a9 J; u if (a < 0 || b < 0) \# f5 m( k' o/ x* [6 D7 ~
  break;   \
" x0 k$ n  Z7 }1 @, m1 W    int tmp = a;     \" z* u7 H6 _7 q; g+ ]# H
    a = b;           \  u: w+ q) c2 L2 s4 X! L
    b = tmp;         \
# ?% T5 E) N) e- u. l% f- S8 G1 B}while(0)

; m- K" x+ L7 V/ S& u2 L/ Q, s2 L* N4 e
int main()
6 I) r/ R: K; S{) x. Y: T9 w% Q) I; l
int var_a = 1;
2 M; p8 k0 j/ y* F. ^" l int var_b = 2;* g; C; E9 k8 ~$ [' a5 D3 U* R
+ `5 q" n8 l! K' W0 }+ ?5 L
if (1)
1 k7 R6 d2 ]7 @9 e    INT_SWAP(var_a, var_b);) }& [* r0 ?& E6 c" O
else6 |; k  s  Q9 _1 d$ K
  printf("hello world!\n");
/ h+ ?0 x8 r- j! i0 D% x! ?* u4 y printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1
: [# I1 a. Y8 Y: w* t1 M! d6 ]3 n  s( D0 i2 F8 I* I
return 0;% c4 Z; f# L8 T) d* E: M+ B4 ^  `
}
! D/ G4 {/ z9 X: i# T; }$ I0 p$ m
6 m0 f+ h9 a( s2 g! h9 F% W
C 语言规定,do{...}while(0) 语法必须使用 ; 作为语句结尾。因此不可能存在以下语句的程序出现:
if (1)
* E+ l$ R! ?) t, H) O, Q; ~6 g   INT_SWAP(var_a, var_b)4 J9 D/ g/ y0 R
else
, H5 H( a. V3 u$ J5 l& K* A{, W1 v  c5 N2 V& p) f2 F. P4 m0 D
printf("hello world!\n");
: w- Z) O5 S, {}9 `& y& ]( i4 e2 l3 p6 r5 y) A

( T0 f3 O4 N5 t9 I6 e- s6 Z; s
优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;支持提前退出函数宏;强制调用时必须使用 ;
  • 缺点:无返回值,不能作为表达式的右值使用。2 j9 t; G( Y' C
4. ({}) 方式
({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。
INT_SWAP 宏使用 ({}) 封装后形态如下:
#define INT_SWAP(a,b)   \
/ D7 [5 z7 Z$ ~6 T, B({                      \
1 b5 q; U- `4 {6 \2 G# i9 G) J' a    int tmp = a;        \! ~8 H. `( f0 d1 [( C
    a = b;              \
; k- N- z. d, j  y    b = tmp;            \
8 U/ m( |, h  M$ o5 J; H8 a! S+ k})

& {, ]2 a& g: _( r" G5 c9 X; @1 _, ^- o# F
do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)  \
8 H; G. c5 W4 a$ m({                 \+ g# |, }, ^& p/ r4 V9 S& V
int tmp = a;    \0 d* Z) |" w* V
a = b;          \1 s9 s9 c1 f0 b6 \) A. d
b = tmp;        \
0 D+ {3 R& s7 ~, x/ X})
5 F% A6 t7 G! o6 |/ A

6 x# T+ k& G) p7 D* X) I6 Wint main()
. i$ S% n4 B7 u+ J( f{7 _( h7 J& L. F+ L
int var_a = 1;
! {" u3 q" I# _0 J: i9 A int var_b = 2;
& Y" @+ L) ^6 I. p7 t
, w$ q; u; H/ G' Y: w( A( q& k if (1)$ }3 H1 J( {) m
    INT_SWAP(var_a, var_b);
. J  }$ X) n: Z+ n: K8 R& T4 p) A+ f else
4 w8 ?( a; W6 l3 T  printf("hello world!\n");
! k' X& _+ n9 ^! h" o! g2 x7 i8 B printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 15 x: c+ N  \6 d- d  X
- d! A; x" K0 F0 e/ T/ k
return 0;& @# g- F! p( e; V# A% L$ z, ^
}
' p/ o2 e# P( g2 k/ E, |- S" ?+ Y8 M9 P/ `
do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢?
答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如:
int main(); K6 J* d, R3 T1 E! _. A
{
: S: j, [% w/ \/ u. B int a = ({
9 l' H7 }0 C# V1 N- f5 R  10;9 p0 R, }" @1 X. B4 X% J/ |
  1000;
* l( W- V/ T) p3 u; L4 y2 o });
2 Z& S) Z3 a1 R1 W4 T( Z$ `- g3 k printf("a = %d\n", a);      // a = 1000
, p1 d% |" h* B: A  m: T& z}
; ~0 y' e7 m1 r8 h/ e; C
6 _, v; F9 k8 K  P4 D% |: z
因此,({}) 可以为函数宏提供返回值。例如:
#define INT_SWAP(a,b)  \
* P$ ~7 a0 }: y: ~/ b2 k({                 \
! v, U5 P& Z' h' R7 w* k int ret = 0;  \6 d  ?! ^) b5 Y) u- Z
if (a < 0 || b < 0) \, V. |+ R" e& N& ^$ b' |
{     \- \" x& _* x6 }: f" @) r
  ret = -1;  \* M0 I% ~, U- N5 n" m/ O, T
}     \7 r( a4 u2 y( v* Z
else    \
/ i+ W7 a4 u5 ?" k3 \* D! N {     \
. Y* X  ^) G, ?/ \4 W  int tmp = a;    \4 V+ ?  Y4 O* O5 E
  a = b;          \# o& j3 ?, R* ^+ |/ I) W
  b = tmp;        \
0 j  n4 U7 l8 q0 l2 r" w }     \- f* G; T) ^) E8 s( e6 V  d$ Z
ret;    \
% f( |3 Y  P4 R3 z})
9 A2 |' V! W; `  h# ]
- U: J0 y# y: Y5 D
int main()7 }/ L0 E: [) C8 F; W
{
# C0 s% g( k$ \% H8 } int var_a = 1;9 i8 s+ h& \$ l) g% G8 n) }, j/ ?
int var_b = 2;% j- t) A* p- E
6 }2 ?9 c0 {& q% q0 i7 [! W" u5 n
if (INT_SWAP(var_a, var_b) != -1)/ y+ \1 |$ ^. i7 r, p2 e
  printf("swap success !!\n");     // swap success !!
4 O8 w1 [) C8 O2 L" \ else3 F) K4 k' f; F, d
  printf("swap fail !!\n"); : Q/ U+ U+ ?/ w5 t" a' F; `
printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 10 @( v! A8 `, Z
$ N7 e2 H: n+ m
return 0;
9 N0 G+ c8 W: @# A5 r4 Z  B}7 g! N" Y2 |, i3 e1 M/ D
可见,此时的 INT_SWAP 宏已与函数十分接近。
优缺点总结:
  • 优点:支持在无花括号且有分支的 if 语句中直接调用;有返回值,支持作为表达式的右值。
  • 缺点:不支持提前退出函数宏;非 C 的原生语法,编译器可能不支持。/ W8 d0 x8 \; T- d" \
5. 总结
综上,在 {}do{...}while(0)({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。
, J! w& [. J2 K
  • TA的每日心情
    开心
    2023-1-3 15:10
  • 签到天数: 2 天

    [LV.1]初来乍到

    2#
    发表于 2020-12-18 14:44 | 只看该作者
    函数宏,即包含多条语句的宏定义
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-24 19:07 , Processed in 0.171875 second(s), 23 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表