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

C程序中比较特殊一点的宏定义do{}while(0)

[复制链接]
gaosmile 发布时间:2020-6-24 13:55
在嵌入式开发中,宏定义非常强大也非常便捷,如果正确使用可以让你的工作事半功倍。
然而,在很多的C程序中,你可能会看到不是那么直接的比较特殊一点的宏定义,比如
do{}while(0)
。
do{conditional code}while(condition)结构

流程图如下: 微信图片_20200624135423.png 一般结构如以下代码

do
0 K/ ?+ p4 i5 B4 \% o{
, D+ U1 h* _7 H  //循环体2 l; b2 v( U0 R$ s
}
" z, s2 H1 K5 z! C: s) Awhile (条件表达式);
7 D3 D! L6 ^" X% a; B6 h! ~
do while/while do的区别
  • do while()+ \; C* k& {  ~4 l- g$ b

意思是先干了再说!!

  • while() do& n8 M0 L$ E) @4 n" r, o

意思是先看看能不能干!

初见do{...}while(0)

linux内核和其他一些开源的代码中,经常会遇到这样的代码:

do{
* G, V6 \9 r, Z' P' a3 ` ...( {* P( t% D# \% _# A7 ?' @) P
}while(0)
8 I* i7 D' e6 k) O

这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,只执行一次而已,那么为什么要这么用呢?

总结了7种妙处

实际上,do{...}while(0)的作用可不止一点点,下面我列举了一些。

  • 有时候只是为了代码分块,比仅仅使用{}更直观些。例如在cocos2d-x代码中
    & \6 H' O! T9 k/ X- ?/ _& b1 r9 A
do
5 k  n7 T" v) `: b* e7 L/ A9 @/ f{+ Y1 {( }0 W% t
    CCImage* pImage = new CCImage();
7 v9 G- r0 ^* F    CC_BREAK_IF(NULL == pImage);
  c- P' d( A* ~  T    bRet = pImage->initWithString(text, (int)dimensions.width, (int)dimensions.height, eAlign, fontName, (int)fontSize);  S+ `" V, ~: C/ J3 c6 ]. F
    CC_BREAK_IF(!bRet);
3 }4 v# Y: T/ z4 n* E/ J$ B    bRet = initWithImage(pImage);
. [4 P1 p' _* N+ C  q    CC_SAFE_RELEASE(pImage);
$ D' V/ l9 N( g. U0 l. f( b} while (0);
  w) t! H5 |1 s2 r6 }, a
  • 为了宏展开的时候不会出错。如果直接放在花括号里会出错的& N  D0 |# x. A- ~$ u' q+ n; K

举例来说,假设你需要定义这样一个宏:

#define DOSOMETHING() action1(); action2();+ b) O% N# P, R

这个宏的本意是,当执行DOSOMETHING()时,action1(),action2()都会被调用。如果有判断,再执行这个宏的话,如下:

if(NULL == pPointer)8 z+ v1 B2 \6 o1 z6 w; f4 h
  DOSOMETHING();
% k' T% G% s% r" [* d. Felse
6 b3 K* Y8 [5 P8 S/ P/ k  ...

, k7 j+ k" Z, L0 f

这样宏在预处理的时候会直接被展开,放在花括号里,那么实际上写的代码如下:

if(NULL == pPointer)
1 N/ y0 b3 c0 @9 d; A, l& q! e    action1();
: J7 B/ T" R% L: `& q' z, Laction2();* w, r$ ?6 o0 N$ E; h; R/ ^* u
else
" U, F4 Y0 b  h1 i) Y  ...

- K7 v+ \% m' T

这展开存在两个问题:

  • 因为if分支后面有两个语句,导致else分支没有对应的if,编译失败。
  • 假设没有else分支,则DOSOMETHING中的第二个语句无论if测试是否通过,都会执行。
    # I/ a8 T: g$ s9 b2 @- {7 F

那么仅仅使用{}把action1()、action2()包起来行么?比如:

#define DOSOMETHING() { action1(); action2(); }% U- v' Z/ e5 n9 H# `/ Y

我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码编译展开后宏就相当于这样写了:{...};,展开后如下:

if(NULL == pPointer)
5 l' c" u* K1 W7 Z  F; j1 _{- x. ]0 A# N, t  o' ^
    action1();2 H6 i/ }, H- b8 x$ f7 Y) ?
    action2();
$ Z: l/ |% Q) a, _) O: g6 ]};
4 N8 J8 u* ]) celse& |; Y5 }' L- z
  ...

# v; r2 l) y- _& ~8 o1 u

这段代码中大括号后多了一个分号,如果有else,那么else又没有对应的if了,编译出错。

那么办法来了

如果我们使用do{...}while(0)来定义宏,即:

#define DOSOMETHING() \
0 `( {1 F# S3 y  S: k1 D7 j5 X        do{ \) V7 x- e1 _6 t+ w! ~. Y6 v" ?. Q
          action1();\+ u; X! F& K$ L/ C$ [6 b
          action2();\, [( r* Z/ J. ~1 x
        }while(0)\
1 Q6 j( I# A3 P) ^( X

宏被展开后,上面的调用语句会保留初始的语义,同时绝大部分编译器都能够识别do{...}while(0)这种无用的循环并进行优化,不会导致性能优化的降低。

小结

在Linux内核和驱动代码还有cocos2d-x中,很多宏实现都使用do{...}while(0)来包裹他们的逻辑,Google的Robert Love(先前从事Linux内核开发)给我们解答如下:

“

让你定义的宏总是以相同的方式工作,不管在调用代码中怎么使用分号和大括号,而该宏总能确保其行为是一致的。

  • 当你执行一段代码到一半,想跳过剩下的一半的时候,如果你正处于do{...}while(0)循环中,则能用break达到这个目的。
      G% Q* Q( R; E( X: D$ ^7 ~
do
# O( T4 O" R1 U# i{
0 b( z# e2 [- o3 y  执行.
/ U5 W- ]4 ?1 [  再执行…
- A$ `1 h- K6 G5 K) e  if (如果有什么条件满足)
3 U3 h( ?4 J. x* m  {0 T5 e' e2 E* u* X" T; p
    我想跳到另外一段代码了,剩下的不执行了,可是不建议用goto语句,怎么办呢?$ a1 X2 }; D: P' @7 a, v/ ]& B
     break;/*搞定*/, l% Q+ }6 l- h8 o' D( V
  }
6 Y5 e7 q6 Z- a  D+ f  我有可能被执行.
1 z( S1 X7 ~6 b- B7 l" g}while(false)
) p( e0 Y: J# M: {0 C: a2 S; g

举个例子如下

do
0 d5 \( x+ A4 C{+ y6 c% ~0 H. d$ @% ~& ^( u. O5 _5 K
  if(!a) break;3 N2 e* N* M' a/ @
  //do something here; k5 \+ @& k2 G6 e6 T
  if(!b) break;8 V) _4 }4 w8 o* g5 r1 K) r& {
  //do another thing here   * N* i# B+ M9 _2 l# P
}while(0);

0 B8 v5 {* o/ d  w; O% _" @9 K5 f6 Z
  • 变形的goto,有些公司不让用goto。在一些函数中,需要实现条件转移,或者构成循环,跳出循环体,使用goto总是一种简单的方法,例如:
    8 c3 f- I. B* S, P0 A' Q
#include <stdio.h>
# p) Q1 J+ r3 y5 F#include <stdlib.h>  o5 k: j9 I* z/ ^6 q9 f
int main()
( Z# v; [2 n9 I2 U( Y1 _{
+ x8 k$ d+ x8 |1 ~  ^/ D9 t   char *str;
4 y3 l; J( @7 }# N2 f
4 R& l& Q; x$ c8 ?7 ?& @8 q   /* 最初的内存分配 */0 E% g: E# y5 w; b8 u0 \7 o) t" g+ T
   str = (char *) malloc(15);) c: ?% V0 x+ `' ?# p) e, T
   if(str != NULL)
# E( A* o# h# ^( T  Y# k) ?     goto loop;
- J/ J, k8 m7 Z " s& c0 N3 H+ j
   printf("hello world\n");
+ X# N% W. r  z; H% B4 x& O1 Z' d
' N4 y! g7 Q4 d) |loop:
% i4 C& v, n! ?$ Y   printf("malloc success\n");
) x7 ]- Q& \, R: s
0 |2 l+ G; T; G) N   return(0);
7 N! f! B3 Q) _% ]  b}
. O- R9 X3 W8 `; q  I+ E  g' F

但由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,这个时候我们可以使用do{...}while(0)来做同样的事情:

#include <stdio.h>
/ E- V- }: f1 T9 `#include <stdlib.h>
+ p: m: U- g! e& f8 q  b3 b+ Qint main(). `) y& c2 Y1 \6 q
{
, R6 ^4 F# K+ O9 x6 l5 S, }  do{& X2 H# U& q3 n; t  F: M2 H
      char *str;
4 N2 `. j! F, U3 s$ ?: Y) L; _7 s5 X% n! p, [
      /* 最初的内存分配 */1 X% \* K/ t& D/ L) p. @
      str = (char *) malloc(15);5 m  D& a; a5 ~9 S; [
      if(str != NULL), P3 ~" b7 ?% V+ T+ J9 G6 Q% r
       break;
( i* m  B- ^* ^/ _5 `0 ?4 Q! X# V0 Z" Y' e7 ^- c$ |
      printf("hello world\n");
& Z# I' H+ R: w. z  }while(0);$ U! W7 m1 i' _$ j1 o8 r4 `# D: j

' i" O2 q, |1 V; K, x6 y) ^; w. Q   printf("malloc success\n");
+ A7 i( M0 k9 j4 |, a& }
4 c  I: n  g: h0 ?  l   return(0);7 p! O2 }6 c6 U, Y2 {
}

% d) _8 }9 W% J) Z- K

这里将函数主体部分使用do{...}while(0)包含起来,使用break来代替goto,后续的清理工作在while之后,现在既能达到同样的效果,而且代码的可读性、可维护性都要比上面的goto代码好的多了。

  • 可以是兼容各种编译器1 C+ J4 w0 I% w3 t5 }. i6 v
int a;/ l# D* R' M2 _$ g& n9 J
a = 10;* N  m+ x/ I+ K/ v8 j- O2 @
int b;  a. S0 x2 V# r5 f) G
b = 20;

( q+ c7 H3 W1 k, F0 K! F

这种代码在只支持c89的编译器上是编译不过去的,比如ADS 2.0。

int a;3 h/ k4 M! v9 I0 T
a = 10;9 f3 q6 \8 Q9 C, B# e+ Z) F$ t
do% ]! l$ Y; v" {0 s! Z6 P7 O, a
{
6 g% B. h" }9 b; y' n3 F9 b   int b;9 A+ q0 e/ W% E5 T/ Q/ d2 d
   b = 20;
9 B) j* @4 X3 [, W; g- |% |}while(0);
9 g" `) J  `7 r" j0 D! A
  • 避免由宏引起的警告 内核中由于不同架构的限制,很多时候会用到空宏。在编译的时候,这些空宏会给出警告,为了避免这样的warning,我们可以使用do{...}while(0)来定义空宏:' ?9 r0 X# ~' Y
#define DOSOMETHING() do{}while(0)
2 T( S0 F9 ~5 D( T# i' M2 ?
  • 定义单一的函数块来完成复杂的操作
    - ^2 p" m/ D7 C4 e& e, _& T

如果你有一个复杂的函数,变量很多,而且你不想要增加新的函数,可以使用do{...}while(0),将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复,例如

int key;- B% A) A/ H/ r. Q% }  s
string value;
+ x" e5 X  \, D. O6 ~int func()
5 z8 |7 b: [1 R: j" @' O' N9 N" P{6 N% [/ u3 @& d
    int key = GetKey();
% L2 I0 k% w" I0 }    string value = GetValue();
1 c- t/ r' K* h: R5 z    dosomething for key,value;  S% h) m8 d' o; o  X; T: B
    do{0 u* K) E1 [3 U( g$ C0 _0 s, A/ X
        int key;string value;
; a, ]) s: h. y        dosomething for this key,value;  T2 @; [% C7 b$ y% e1 q- s
    }while(0);   
8 Q9 D6 d0 }% K" Q  p}

" ^5 K% \% \, i; I' j! N7 |

但是为了代码的可读性,尽量声明不同的变量名,以便于后续开发人员欣赏


7 K: y' d' ^8 p! B0 T' l. B
赞 收藏 评论0 发布时间:2020-6-24 13:55

举报

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