前言
在一个群里面看到一个人问,do-while(0)语句有什么用?do-while(0)这个程序最终结果不应该就是程序只跑一次,那么写和不写有什么区别呢?
do-while(0)在复杂宏定义上的优点
为什么需要复杂宏
(1)在讲解do-while(0)在复杂宏定义上的优点前,我先介绍一下复杂宏的好处。
(2)当我们看到一个宏定义非常复杂的时候,我们很自然而然的会想到,为啥不让他变成一个函数呢?在宏定义里面搞到这么花里胡哨的,不如直接定义一个函数来的方便。
(3)当我带着这么问题去询问了交流群之后,一个大佬马上给出了回复。
<1>他说他当年一个项目8位单片机,函数都不敢深入调用,好多都是写的宏函数。因为资源有限。
<2>大佬介绍到,他哪一款单片机,硬件堆栈,好像就7层,中断预留两层,有些c标准函数用两层堆栈,自己写的就限制在3-4层左右。因为堆栈的有限,怕因为函数的深入调用如果过于频繁或递归层数过深,可能导致栈空间不足,发生堆栈溢出。当栈空间不足以容纳新的栈帧时,程序会崩溃或异常终止。
<3>所以他们当年都是这样写代码
优点介绍
(1)当我们在编写业务逻辑的时候,可能可能需要定义一个比较复杂的宏。如下
#define fun printf("hello"); printf("world") int main() { fun; return 0; }
(2)在这个宏的基础上,我们增加一个判断语句,就会发现问题所在。
/******* c文件 *******/ #define fun printf("hello"); printf("world") int main() { int a=0; if (a == 0) fun; else ... return 0; } /******* 预处理之后 *******/ int main() { int a=0; if (a == 0) printf("hello"); printf("world"); else ... return 0; }
(3)通过上面的代码,我们明显的发现了。如果是在if语句中,执行这个宏,会发现第二个语句不会包含在if语句中。这样就会导致,因为 if 分支后有两个语句,导致 else 分支没有对应的 if,编译失败。
(4)这个时候可能就会有人说了,那我没有else语句呢?毫无疑问,可以编译通过,但是那样的话,printf(“world”);无论如何都会执行。
(5)上述问题无疑会给程序带来异常,那么do-while(0)的用途就出来了。
<1>处理了因为if语句所带来的问题。
<2>让程序更加美观。因为在每一条语句背后,我们都习惯性的加上“;”,但是在没有加do-while(0)的宏里面,最后一条语句不能加上“;”。这样容易导致阅读障碍。
/******* c文件 *******/ #define fun do{ \ printf("hello"); \ printf("world"); \ }while(0) int main() { int a=0; if (a == 0) fun; else ... return 0; } /******* 预处理之后 *******/ int main() { int a=0; if (a == 0) do{printf("hello");printf("world");}while(0); else ... return 0; }
do-while(0)对于程序释放的优缺点
优点
(1)我们在Linux内核中,常常能够看到goto语句,用于进行一些程序的收尾工作。(比如卸载驱动,销毁设备号,注销GPIO)
(2)但是因为goto不符合软件工程的结构化,而且有可能使得代码难懂,而且很容易造成bug,被一些人戏称为,C语言禁术。
/******* 使用goto *******/ void test_func(void) { //申请资源 。。。 if (!condition1) { goto exit_entry; } //执行一些逻辑 if (!condition2) { goto exit_entry; } //执行一些逻辑 if (!condition3) { goto exit_entry; } //执行一些逻辑 exit_entry: //释放资源 。。。 } /******* 使用do-while(0) *******/ void test_func(void) { //申请资源 。。。 do { if (!condition1) { break; } //执行一些逻辑 if (!condition2) { break; } //执行一些逻辑 if (!condition3) { break; } //执行一些逻辑 } while(0); exit_entry: //释放资源 。。。 }
缺点
(1)既然do-while(0)比goto好,那为什么大佬们依旧喜欢使用goto呢?
(2)因为在Linux驱动开发过程中,我们需要申请很多东西,一旦其中一个东西申请失败。就需要将前面申请的都释放掉,然后返回错误。
(3)可能还是有人没有明白。我现在举个例子,一个驱动程序,需要先完成A任务,再完成B任务,最后完成C任务。然后任务结束,就先执行c任务,然后执行b任务,最后执行a任务。大写字母的任务表示创建,小写字母的任务表示释放。他们执行顺序刚好相反。
// 创建 void create() { A; B; C; } //销毁 void destroy() { a; b; c; }
(4)以上是按照一切顺利执行来编写程序的。但是,如果我A任务,B任务或者C任务有一个执行失败了。那怎么办呢?
(5)因为我们知道,如果A任务执行失败,只需要进行a任务。而B任务执行失败,就需要进行执行b和a了。如果使用do-while(0)语句,显然就很麻烦。而使用goto就会方便很多。
// 创建 void create() { A; if(A创建失败) goto A_err; B; if(B创建失败) goto B_err; C; if(A创建失败) goto C_err; C_err: c; B_err: b; A_err: a; }
结论
因此,我们可以得出,如果一个函数最终的出口是固定的,而且有多个if判断语句执行的情况,使用do-while(0)更好。而如果函数出口不固定,按照顺序执行,那么goto就会更优秀。