goto问题
2016-08-04 08:52
239 查看
这两天帮着review一段代码,正好看到下面的一个函数,
看过觉得这段代码中出现一个很熟悉的关键字 goto,学c语言都应该有被无数次的教导过不要用goto的经历。到底为什么不建议用goto语句呢,当时也没有仔细研究,现在想来应该有以下几个原因:
从中我们可以总结goto的几个可以使用的情况:
在注释的收很容易遇到字符集的问题,不同的操作系统下甚至不同的文本编辑器都会可能看到一堆乱码。这样的goto的标签也起到了注释的作用。这些标签说明一个函数内程序片段的作用,将函数进行更小的模块化。
不要大量使用
不要向前跳
注意堆栈
UINT32 fh_i2c_read(UINT32 device, UINT32 raddr, UINT32 mode) { UINT32 high_rdata, low_rdata; UINT8 high_raddr = (raddr>>8)&0xff; UINT8 low_raddr = raddr&0xff; UINT32 i2cDataCmd, i2cStatus; // UINT32 valid; i2cDataCmd = 0x98600010; i2cStatus = 0x98600070; UINT32 error_cnt = 0; high_rdata = low_rdata = 0; REREAD: i2c_init(device); _ASM("flag 0"); // int disable if ( ((mode>>1) & 1) == 0 ) { outw(i2cDataCmd,low_raddr); } else { outw(i2cDataCmd,high_raddr); outw(i2cDataCmd,low_raddr); } _ASM("flag 6"); while( 4!=(inw(i2cStatus) & 0x5) ); _ASM("flag 0"); // int disable if ( (mode & 1) == 0 ) { outw(i2cDataCmd,0x100); //issue read } else { outw(i2cDataCmd,0x100); outw(i2cDataCmd,0x100); } _ASM("flag 6"); if ( (mode & 1) == 0 ) { while( 0x8 !=(inw(i2cStatus) & 0x9) ); //recevie FIFO not empty low_rdata = inw(i2cDataCmd); } else { while( 0x8 !=(inw(i2cStatus) & 0x9) ); high_rdata = inw(i2cDataCmd); while( 0x8 !=(inw(i2cStatus) & 0x9) ); low_rdata = inw(i2cDataCmd); } wait(1000); if( 0x0 !=(inw(i2cStatus) & 0x9) ) { error_cnt++; i2c_disable(); if(error_cnt <= TRY_CNT) { goto REREAD; } else { timeout = i2c_read_timeout; return i2c_read_timeout; } } i2c_disable(); return((high_rdata<<8)|low_rdata); }
看过觉得这段代码中出现一个很熟悉的关键字 goto,学c语言都应该有被无数次的教导过不要用goto的经历。到底为什么不建议用goto语句呢,当时也没有仔细研究,现在想来应该有以下几个原因:
可读性差
对于大量使用goto的代码,极端情况下的十几行代码五六个goto跳转的根本无法记住整个流程的顺序结构。对于没有IDE帮助,函数又非常长的情况,这种跳转函数看起来需要不停的上下翻页,除了作者接触代码的都会疯甚至发生流血事件。可维护性差
上面已经说了不容易看懂,既然不容易看懂就更谈不上改了。代码里跳转太多,在里面添加任何逻辑都有可能导致程序会无法完成正常运转。goto需要的跳转标签需要在行首,在不规范的代码很容易淹没在正常逻辑中,容易在标签的命名上产生重复。流程可控性差
现在的软件开发一般都不会是单兵作战了,多人协作过程中忌讳使用复杂的逻辑流程,过程复杂很容易让协作者不容易进入状态。性能损失
goto是由硬件汇编衍生过来的,对应汇编的jump或者long jump。但是在过去时间里面cpu的主频比较低,但是还是会有基本的流水线结构,一般执行当前指令时,会预取两条指令存储在寄存器中。但是goto回直接让cpu进行跳转,这样的结果就是预取的指令用不上了,然后需要重新预取指令进行执行。早一些的cpu的跳转能力还很差,多次的跳转会让cpu性能损失比较严重。是不是goto就一无是处呢?
当然不是!对应linux kernel中代码看一下AT32AP700x的rtc代码static int __init at32_rtc_probe(struct platform_device *pdev) { struct resource *regs; struct rtc_at32ap700x *rtc; int irq; int ret; rtc = kzalloc(sizeof(struct rtc_at32ap700x), GFP_KERNEL); if (!rtc) { dev_dbg(&pdev->dev, "out of memory\n"); return -ENOMEM; } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) { dev_dbg(&pdev->dev, "no mmio resource defined\n"); ret = -ENXIO; goto out; } irq = platform_get_irq(pdev, 0); if (irq <= 0) { dev_dbg(&pdev->dev, "could not get irq\n"); ret = -ENXIO; goto out; } rtc->irq = irq; rtc->regs = ioremap(regs->start, regs->end - regs->start + 1); if (!rtc->regs) { ret = -ENOMEM; dev_dbg(&pdev->dev, "could not map I/O memory\n"); goto out; } spin_lock_init(&rtc->lock); /* * Maybe init RTC: count from zero at 1 Hz, disable wrap irq. * * Do not reset VAL register, as it can hold an old time * from last JTAG reset. */ if (!(rtc_readl(rtc, CTRL) & RTC_BIT(CTRL_EN))) { rtc_writel(rtc, CTRL, RTC_BIT(CTRL_PCLR)); rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); rtc_writel(rtc, CTRL, RTC_BF(CTRL_PSEL, 0xe) | RTC_BIT(CTRL_EN)); } ret = request_irq(irq, at32_rtc_interrupt, IRQF_SHARED, "rtc", rtc); if (ret) { dev_dbg(&pdev->dev, "could not request irq %d\n", irq); goto out_iounmap; } platform_set_drvdata(pdev, rtc); rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &at32_rtc_ops, THIS_MODULE); if (IS_ERR(rtc->rtc)) { dev_dbg(&pdev->dev, "could not register rtc device\n"); ret = PTR_ERR(rtc->rtc); goto out_free_irq; } device_init_wakeup(&pdev->dev, 1); dev_info(&pdev->dev, "Atmel RTC for AT32AP700x at %08lx irq %ld\n", (unsigned long)rtc->regs, rtc->irq); return 0; out_free_irq: platform_set_drvdata(pdev, NULL); free_irq(irq, rtc); out_iounmap: iounmap(rtc->regs); out: kfree(rtc); return ret; }
从中我们可以总结goto的几个可以使用的情况:
一个函数中多次执行程序片段
可以看到上面的程序中对于初始化失败后需要执行错误处理,有几个地方出错处理是一样,但是又不想把出错处理包装成函数,把出错处理程序在每个需要的地方复制粘贴又太傻,goto这时候就很有价值。可以当程序中注释
void func() { int x; ...... if (x) goto err; err: .... }
在注释的收很容易遇到字符集的问题,不同的操作系统下甚至不同的文本编辑器都会可能看到一堆乱码。这样的goto的标签也起到了注释的作用。这些标签说明一个函数内程序片段的作用,将函数进行更小的模块化。
多层循环的跳出
程序如果存在多层的循环,在循环中如果想要退出的话可能需要多个的break才能实现跳出,但是goto却是一个很简化的方法。int i,j; for( i;i<xxx;i++){ for(j;j<xxx;j++){ if(xx) goto out; } } out:
goto 需要注意什么
写程序跟社会上的其他事情都一样,专家大神讲的都被奉为圣旨,严格遵守少数人订立的规则,却每天都在见到乱拳打死老师傅的情景。所以也就随着自己认为的原则就好了。对一般水平的大众来说goto可以用但是最好注意以下几条:不要大量使用
不要向前跳
注意堆栈
最后
goto使用的讨论一直持续,各路大神小妖都有自己的一套理由和结论。听说还有人专门写了论文来研究,个人觉的还是使用多了根据自己的情况来决定是否使用和怎么使用吧。相关文章推荐
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 694 - The Collatz Sequence---关于goto语句的一些反思(还有uva的Runtime Error问题的解决)
- goto对资源的释放问题的解决
- 转:switch内部的变量定义问题(goto类似)
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- goto语句会引起程序混乱?使用goto解决实际问题
- goto语句的“跳跃”问题
- JS正则替换问题 http://bbs.51js.com/redirect.php?fid=1&tid=67418&goto=nextnewset
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 存储过程中,goto语句和if else语句注意,标签的位置问题导致条件语句失效
- 数据导入问题参考http://www.itpub.net/redirect.php?tid=888849&goto=lastpost
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 关于goto语句能不能从复杂的嵌套循环判断中跳出去的问题讨论
- 返回值问题及goto问题
- 新人问题:C语言中goto的用法