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使用的討論一直持續,各路大神小妖都有自己的一套理由和結論。聽說還有人專門寫了論文來研究,個人覺的還是使用多了根據自己的情況來決定是否使用和怎麼使用吧。
相關文章
- goto?Go
- break,continue,gotoGo
- continue、break與gotoGo
- goto語句簡整Go
- java有沒有gotoJavaGo
- 遞迴與goto (轉)遞迴Go
- C++ goto語句C++Go
- C語言goto語句以及用goto語句構成迴圈C語言Go
- PHP goto操作符使用PHPGo
- bilibili_class公開課補充_goto_strcmp_system_面試題Go面試題
- 臭名遠揚之 goto 語句Go
- goto 語法在 PHP 中的使用GoPHP
- GO語言————5.6 標籤與gotoGo
- break、continue與goto關鍵字Go
- Python3 goto 語句的使用PythonGo
- PHP5.3新增操作符之gotoPHPGo
- c# case多分去與跳轉gotoC#Go
- Day16 break-continue-gotoGo
- 8.19 被誤解的關鍵字:gotoGo
- 愚人節惡作劇:Rust的“goto”實現RustGo
- GOTO語句在PL/SQL中的應用GoSQL
- c# while-do while-foreach-if-gotoC#WhileGo
- 發現問題,解決問題
- 輸出1-100,不使用迴圈 goto 遞迴Go遞迴
- 【程式設計好習慣】恰當使用goto語句薦程式設計Go
- 解決「問題」,不要解決問題
- WebService 訪問問題Web
- 面試反問問題面試
- 提問題比解決問題更重要
- 一個非技術問題的問題
- Java服務.問題排查.問題復現Java
- MySQL 問題MySql
- 【Git】問題Git
- UIImageJPEGRepresentation 問題UI
- JS問題JS
- pycharm問題PyCharm
- beego問題Go
- 字串問題字串