嵌入式C中的goto语句,争议很大
什么是goto语句?
goto
语句被称为 C 语言中的跳转语句,用于无条件跳转到其他标签。它将控制权转移到程序的其他部分。
goto
语句一般很少使用,因为它使程序的可读性和复杂性变得更差。
语法
goto label;
goto 语句示例
让我们来看一个简单的例子,演示如何使用 C 语言中的 goto 语句。
打开 Visual Studio 创建一个名称为:goto 的工程,并在这个工程中创建一个源文件:goto-statment.c
,其代码如下所示:
#include void main(){ int age;gotolabel: printf("You are not eligible to vote!n"); printf("Enter you age:n"); scanf("%d", &age); if (age < 18) { goto gotolabel; } else { printf("You are eligible to vote!n"); }}
执行上面代码,得到以下结果
You are not eligible to vote!Enter you age:12You are not eligible to vote!Enter you age:18You are eligible to vote!
为什么它这么不受待见?
二十几年前,当计算机编程尚处于起步阶段时,程序流程是由 “GOTO” 语句来控制。该类语句允许程序员对当前代码行断行,而直接进入另一个不同的代码段。
下图为简单的示例。
编程语言终究开始引入了函数的概念,即允许程序对代码进行断行。如果已经完成,不再使用 goto 语句来表示代码的断行。
函数调用后,函数将回到下一条指令。下图为示例。
这一做法改善了程序结构,提高了可读性。自此,这被视为编写程序的正确方法。只要看到或想到 goto 语句,就会让软件工程师退缩,产生本能的厌恶。
在 wikipedia 上的解释就是:
GOTO语句一直是批评和争论的目标,主要的负面影响是使用GOTO语句使程序的可读性变差,甚至成为不可维护的「面条代码」。
随着结构化编程在二十世纪六十年代到七十年代变得越来越流行,许多计算机科学家得出结论,即程序应当总是使用被称为「结构化」控制流程的命令,以及 if-then-else
语句来替代 GOTO。甚至在今天,许多程序风格编码标准禁止使用 GOTO 语句。
也有不少人为 GOTO 语句辩护,他们认为只要加以限制地使用 GOTO 语句不会导致低质量的代码,并且在许多编程语言中,一些功能难以在不使用 GOTO 语句的情况下实现。比如有限状态机的实现、跳出嵌套循环以及异常处理等等。
大概最著名的对于 GOTO 的批评是艾兹格·迪杰斯特拉(Edsger Wybe Dijkstra)在1968年的一篇名为《GOTO陈述有害轮》的论文。
迪杰斯特拉认为不加限制地使用GOTO语句应当从高级语言中废止,因为它使分析和验证程序正确性(特别是涉及循环)的任务变得复杂。
另外一种观点出现在高德纳的Structured Programming with go to Statements
中,文章分析了许多常见编程任务,然后发现其中的一些使用GOTO将得到最理想的结构。
限制GOTO
许多语言,如 C 语言和 Java,提供了相关的控制流语句,如 break
和 continue
,它们都是有效地被限制的 goto 语句。它们的作用是无条件跳转,但是只能够跳到循环块结束的位置 —— 继续进入下一循环(continue)或者结束循环(break)。
switch/case结构
C 语言、C++ 和 Java 中的 switch 语句高效地实现了一个多路 goto,跳转目标由表达式的值来选择。这也导致了我们没有不得不使用 goto 的理由。
针对这些,导致目前 goto 的使用情况是这样的:goto 语句的结果在C/C++等高级编程语言中保留了goto语句,但被建议不用或少用。
在一些更新的高级编程语言,如 Java 不提供 goto 语句,它虽然指定 goto 作为关键字,但不支持它的使 用,使程序简洁易读;尽管如此后来的 c# 还是支持 goto 语句的,goto 语句一个好处就是可以保证程序存在唯一的出口,避免了过于庞大的 if 嵌套。
另一方面,goto 语句只是不提倡,当然不是禁用,那么在什么情况下可以使用 goto 语句呢?
可以考虑使用 goto 的情形:
· 从多重循环中直接跳出 ;
· 出错时清除资源;
· 可增加程序的清晰度的情况。
不加限制地使用 goto:破坏了清晰的程序结构,使程序的可读性变差,甚至成为不可维护的"面条代码"。
经常带来错误或隐患,比如它可能跳过了某些对象的构造、变量的初始化、重要的计算等语句。
下列关于使用 goto 语句的原则可以供读者参考。
1. 使用 goto 语句只能 goto 到同一函数内,而不能从一个函数里 goto 到另外一个函数里。
2. 使用 goto 语句在同一函数内进行 goto 时,goto 的起点应是函数内一段小功能的结束处,goto 的目的 label 处应是函数内另外一段小功能的开始处。
3. 不能从一段复杂的执行状态中的位置 goto 到另外一个位置,比如,从多重嵌套的循环判断中跳出去就是不允许的。
4. 应该避免像两个方向跳转。这样最容易导致"面条代码"。
阅读过 linux 内核代码的同学应该注意到,linux 内核代码里面其实有不少地方用了 goto 语句,
这是在/drivers/i2c/i2c-dev.c
中的i2c_dev_init
函数:
static int __init i2c_dev_init(void){ int res; pr_info("i2c /dev entries drivern"); res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); if (res) goto out; i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); if (IS_ERR(i2c_dev_class)) { res = PTR_ERR(i2c_dev_class); goto out_unreg_chrdev; } i2c_dev_class->dev_groups = i2c_groups; /* Keep track of adapters which will be added or removed later */ res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); if (res) goto out_unreg_class; /* Bind to already existing adapters right away */ i2c_for_each_dev(NULL, i2cdev_attach_adapter); return 0;out_unreg_class: class_destroy(i2c_dev_class);out_unreg_chrdev: unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);out: pr_err("Driver Initialisation failedn"); return res;}
但是你会发现,这些地方的goto
语句,使用非常谨慎,基本都遵循上面提到的几个原则。
本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW
或用微信扫描左侧二维码