嵌入式C语言,我劝你少用GOTO语句!

Posted 小麦大叔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式C语言,我劝你少用GOTO语句!相关的知识,希望对你有一定的参考价值。

我是小麦,一名热爱电子技术的工程师

关注我,一起变得更加优秀!

什么是goto语句?

goto语句被称为C语言中的跳转语句。

用于无条件跳转到其他标签。它将控制权转移到程序的其他部分。

goto语句一般很少使用,因为它使程序的可读性和复杂性变得更差。

语法

goto label;

goto 语句示例

让我们来看一个简单的例子,演示如何使用C语言中的goto语句。

打开Visual Studio创建一个名称为:goto的工程,并在这个工程中创建一个源文件:goto-statment.c,其代码如下所示 -

#include <stdio.h>  
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:
12
You are not eligible to vote!
Enter you age:
18
You are eligible to vote!

为什么它这么不受待见?

二十几年前,当计算机编程尚处于起步阶段时,程序流程是由“GOTO”语句来控制。

该类语句允许程序员对当前代码行断行,而直接进入另一个不同的代码段。

列表1为简单的示例。

使用GOTO语句

编程语言终究开始引入了函数的概念,即允许程序对代码进行断行。

如果已经完成,不再使用goto语句来表示代码的断行。

函数调用后,函数将回到下一条指令。列表2为示例。

用函数控制流程

这一做法改善了程序结构,提高了可读性。自此,这被视为编写程序的正确方法。

只要看到或想到goto语句,就会让软件工程师退缩,产生本能的厌恶。

在wikipedia上的解释就是;

GOTO语句一直是批评和争论的目标,主要的负面影响是使用GOTO语句使程序的可读性变差,甚至成为不可维护的「面条代码」。

随着结构化编程在二十世纪六十年代到七十年代变得越来越流行,许多计算机科学家得出结论,即程序应当总是使用被称为「结构化」控制流程的命令,以及if-then-else语句来替代GOTO。

甚至在今天,许多程序风格编码标准禁止使用GOTO语句。

也有不少人为GOTO语句辩护,他们认为只要加以限制地使用GOTO语句不会导致低质量的代码,并且在许多编程语言中,一些功能难以在不使用GOTO语句的情况下实现。

比如有限状态机的实现、跳出嵌套循环以及异常处理等等。

大概最著名的对于GOTO的批评是艾兹格·迪杰斯特拉(Edsger Wybe Dijkstra)在1968年的一篇名为《GOTO陈述有害轮》的论文。[2]

迪杰斯特拉认为不加限制地使用GOTO语句应当从高级语言中废止,因为它使分析和验证程序正确性(特别是涉及循环)的任务变得复杂。

另外一种观点出现在高德纳的Structured Programming with go to Statements [3]中,文章分析了许多常见编程任务,然后发现其中的一些使用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 driver\\n");

 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 failed\\n");
 return res;

但是你会发现,这些地方的goto语句,使用非常谨慎,基本都遵循上面提到的几个原则。

—— The End ——

往期推荐

嵌入式进阶必看!7个硬核的 C 语言要点

嵌入式 C 语言中三块难啃的硬骨头

嵌入式 C 语言的八大难点揭秘

芯片在显微镜下,有哪些不为人知的秘密?

分享几个超级实用的C语言小技巧!

学妹为了学习数电,自制555芯片

长按识别二维码关注我

你点的每个好看,我都认真当成了喜欢

以上是关于嵌入式C语言,我劝你少用GOTO语句!的主要内容,如果未能解决你的问题,请参考以下文章

goto语句在工作当中的用法

我劝你不要记账

蓝桥杯,我劝你不要参加的8个完美理由

Docker: CPU我劝你善良!!

CSDN日报20170401 ——《如果你还是“程序员”,我劝你别创业!》

我劝你写文章