为什么要用do{…}while(0)

内容分享3天前发布
0 0 0

1.协助定义复杂的宏以避免错误

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

#define DOSOMETHING() foo1(); foo2();			 

这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:

if(a>0) DOSOMETHING();

由于宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:

if(a>0) foo1(); foo2();

这就出现了问题,由于无论a是否大于0,foo2()都会被执行,导致程序出错。

那么仅仅使用{}将foo1()和foo2()包起来行么?列如:

#define DOSOMETHING() { foo1(); foo2(); }

我们在写代码的时候,都习惯在语句后面加上分号,如果在宏中使用{},代码里就相当于写了:”{…};”,展开后就是这个样子:

if(a>0) { foo1(); foo2(); };

很明显,这是一个语法错误(大括号后面多了一个分号)。

在所有可能的情况下,期望我们写的多语句的宏总能正确的表现几乎是不可能的。你不能让宏表现的很函数一样—在没有do/while(0)的情况下。

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

#define DOSOMETHING() 
						do { foo1(); foo2(); } while(0)

这样,宏展开后,上面的调用语句才会保留初始的语义。do能确保大括号里的逻辑被执行,而while(0)能确保该逻辑只被执行一次,就像没有循环语句一样。

总结:在linux和其他代码库里的许多宏实现都使用do/while(0)来包裹他们的逻辑,这样不管在调用代码中怎么使用分号和大括号,而该宏总能确保其行为是一致的。

2.避免使用goto控制程序流程

在一些函数中,我们可能需要在return语句之前做一些清理工作,列如释放在函数开始处有malloc申请的内存空间,使用goto总是一种简单的方法:

int foo() {
  somestruct* ptr = malloc(...);
  dosomething...;
  if(error) goto END;
  dosomething...;
  if(error) goto END;
  dosomething...;
END:
  free(ptr);
  return(0);
}

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

int foo() {
  do {
    somestruct* ptr = malloc(...);
    dosomething...;
    if(error) break;
    dosomething...;
    if(error) break;
    dosomething...;
  }
  free(ptr);
  return(0);
}

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

3.避免由宏引起的警告

#define EMPTYMICRO do{}while(0)

linux内核中由于不同架构的限制,许多时候会用到空宏。在编译的时候,这些空宏会给出warning,为了避免这样的warning,我们可以使用do{}while(0)来定义宏。

4.定义单一的函数块来完成复杂的操作

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

int key;
stirng value;
int func() {
  int key = GetKey();
  string value = GetValue();
  dosomething for key, value;
  do {
    int key, string value;
    dosomething for this key, value;
  }while(0);
}

这种情况是指一个变量多处使用(但每处的意义还不同),我们可以在每个do-while中缩小作用域。

© 版权声明

相关文章

暂无评论

none
暂无评论...