
1. 函数宏介绍 函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。 函数宏本质上为宏,可以直接进行定义,例如: #define INT_SWAP(a,b) \int tmp = a; \ a = b; \ 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- ?{ \ int tmp = a; \ 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; \ a = b; \ b = tmp; \ }% G# I' o, l/ L/ u6 F " o# `- _: ?9 @+ |' N ^+ ]$ R int main()0 q7 M( \3 g, o0 E: [" u) J { int var_a = 1; int var_b = 2;- b1 t X6 ?# E 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 if (1) 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); else7 A% W# w) J/ ^$ d; {, N- F+ Q0 k" [ printf("hello world!\n"); 会发现编译出错: .../mnt/hgfs/share/pr_c/src/main.c: In function ‘main’: /mnt/hgfs/share/pr_c/src/main.c:18:2: error: ‘else’ without a previous ‘if’2 N: Q9 Z* M W else 这是因为 INT_SWAP(var_a, var_b); 最后的 ; 已经把 if 的作用域终结了,后续的 else 当然没有找到与之匹配的 if 了。 因此,解决方法有两种,分别为不使用 ;(port.1)或规定必须使用带花括号的 if(port.2),例如: /* port.1 */if (1)3 r, Q1 K0 q' f3 G* e0 P+ s- e1 X INT_SWAP(var_a, var_b) else { printf("hello world!\n"); } - P% q5 X, a1 i- n0 L* V" A4 T /* port.2 */ if (1) {% [- ?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 { printf("hello world!\n");& T0 k/ K1 t( x0 I/ r } 可见,不使用 ; 的调用方式无论从程序阅读还是使用方法方面都是十分别扭的;而规定必须使用带花括号的 if 的调用方式有违常理的,因为宏函数应该适用于任何语法。 优缺点总结: 3. do{...}while(0) 方式 INT_SWAP 宏使用 do{...}while(0) 封装后形态如下: #define INT_SWAP(a,b) \6 |* _& z+ l& G x$ M1 q1 K! q" edo{ \ 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) do{...}while(0) 表示只执行一遍 {} 内的语句,表象来说与 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函数宏、整合为一条语句与强制调用时必须使用 ;。 由于 do{...}while(0) 实际为 while 循环,因此可以使用关键字 break 提前结束循环。利用该特性,可以为函数宏添加参数检测。例如: #define INT_SWAP(a,b) \do{ \ if (a < 0 || b < 0) \ break; \) V/ ?, ~: d/ R4 A1 C, { e int tmp = a; \ a = b; \ 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) \do{ \ if (a < 0 || b < 0) \ break; \/ f D* G2 }2 y6 ?: d int tmp = a; \% Y" t- ? Y v6 r a = b; \ b = tmp; \ }while(0) int main() { int var_a = 1;* M! G r7 X. c' H4 V int var_b = 2; if (1) INT_SWAP(var_a, var_b);, V' m0 w- l/ `5 o0 Q0 W else printf("hello world!\n"); 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; }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- AINT_SWAP(var_a, var_b) else& p# p3 f: A1 a" ` H {2 X6 D& \7 I' Y printf("hello world!\n"); 8 h0 }! J/ ~6 J; [% p } 8 H- S, V6 @& F 优缺点总结: 4. ({}) 方式 ({}) 为 GNU C 扩展的语法,非 C 语言的原生语法。 INT_SWAP 宏使用 ({}) 封装后形态如下: #define INT_SWAP(a,b) \( ]4 @+ X7 v5 ?& e* S& E2 e({ \ int tmp = a; \ a = b; \ b = tmp; \ }) 与 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; \ b = tmp; \ }). u! `0 g1 `0 {. |/ f1 }; P3 F6 I; c int main() { int var_a = 1;) U+ j" v8 {' N int var_b = 2;3 B4 n8 r( {8 h2 i0 O if (1)8 N9 g2 H* p6 M& L: w& A7 i& r INT_SWAP(var_a, var_b); else" M: M1 a) l, r8 k( q printf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 return 0; } " Y1 A. r5 A3 Z' t6 e 与 do{...}while(0) 不同的是,({}) 不能提前退出函数宏与支持返回值。({}) 毕竟不是 while 循环,不能直接使用 break退出函数宏是比较容易理解。那支持返回值是什么意思呢? 答案是 C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值。例如: int main(){ int a = ({ 10; 1000;2 ]" A5 F7 D( s" I }); 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; \ } \" s9 m+ ?( }4 e3 h+ {! N! E5 M else \ { \ 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 @ int main(). |. N2 y2 f( r8 D. t { int var_a = 1; 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 printf("swap fail !!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1 return 0;% M! }) \! F5 n2 v2 g }& i- h: M1 T; ?: x3 o7 ?6 T 可见,此时的 INT_SWAP 宏已与函数十分接近。 优缺点总结: 5. 总结 综上,在 {}、do{...}while(0) 和 ({}) 这三种函数宏的封装方式之中,应尽可能不使用 {},考虑兼容性一般选择使用 do{...}while(0),当需要函数宏返回时可以考虑使用 ({}) 或直接定义函数。 5 @$ V+ u+ E* }/ O* M0 B* t- H |