goto问题

本文探讨了goto语句在编程中的使用,分析了其在可读性、可维护性和流程可控性等方面的缺点,并通过实际代码示例展示了在特定场景下使用goto的优势。

这两天帮着review一段代码,正好看到下面的一个函数,

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使用的讨论一直持续,各路大神小妖都有自己的一套理由和结论。听说还有人专门写了论文来研究,个人觉的还是使用多了根据自己的情况来决定是否使用和怎么使用吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值