Re: [問卦] C語言寫goto為啥會被排擠消失

看板Gossiping作者時間7年前 (2016/11/06 00:07), 7年前編輯推噓27(2817)
留言36則, 26人參與, 最新討論串4/4 (看更多)
※ 引述《newpuma (還很新)》之銘言: : 安安各位資源回sow工程師們大家好 : 安安各位資訊相關工具人們大家好 : 為啥在C語言寫goto會被酸呢 : 「你一定很廢才要寫這個!哈!」、「天啊不是說好不提goto嗎??」、「這種東西我看 : 不懂,重寫!!」 : 為蛇摸寫goto就要被排擠呢 : 有八卦嗎? goto使用得當的的話其實程式可讀與可維護性會不錯 下面有幾個例子 記憶體集中釋放 解決深度巢狀 誤用goto破壞程式結構 濫用goto 例子舉得不好的地方還請先進指教 例如在此函數分配並被使用的記憶體,利用goto集中在結束點釋放 int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr 已經指向一個成功分配的記憶體空間 */ ret = function_a(ptr); if (ret != ERR_NONE) { report_error_log(ret); free(ptr); return ret; } ret = function_b(ptr); if (ret != ERR_NONE) { report_error_log(ret); free(ptr); return ret; } ... return ret; } 上述的寫法會讓釋放ptr變得很麻煩 若改用下面的寫法 int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr 已指向一個成功分配的記憶體空間 */ ret = function_a(ptr); if (ret != ERR_NONE) { report_error_log(ret); goto _exit; } ret = function_b(ptr); if (ret != ERR_NONE) { report_error_log(ret); goto _exit; } ... report_function_done_log(); _exit: free(ptr); return ret; } 假設function_a出錯其後的函數也不用跑 就跳到_exit標籤結束執行 ptr結束任務了釋放它 一個函數內使用多個指標ptr ptr2時作法也差不多 題外話,一個函數用到三個指標以上的時候 就要思考這個function某些地方是不是該抽象成子函數了 再來是深度巢狀的問題 int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr 已經指向一個成功分配的記憶體空間 */ ret = function_a(ptr); if (ret == ERR_NONE) { ret = function_b(ptr); if (ret == ERR_NONE) { ... 在某個巢狀 report_function_done_log(); } else { report_error_log(ret); free(ptr); return ret; } } else { report_error_log(ret); free(ptr); return ret; } return ret; } 深度巢狀排版很煩減少可維護性也減少可讀性 若能用第一個例子改寫後的寫法會清楚些 曾看過使用goto破壞結構化程式的例子 例如在for迴圈裡面用goto int ret = ERR_NONE; int i = 0; for (; i < N; i++) { ret = function_x(i); if (ret) { report_error_log(ret); goto _exit; } } ... _exit: do_something(); return ret; 上述的寫法破壞for迴圈結構,運氣好也許不會在執行錯誤時發生不可預期行為 運氣不好產生臭蟲就難除錯 保持for迴圈結構可以這樣寫 int ret = ERR_NONE; int i = 0; for (; i < N; i++) { ret = function_x(i); if (ret) { report_err_log(); break; } } if (ret != ERR_NONE) goto _exit; ... _exit: do_something(); return ret; 這樣寫for迴圈發生錯誤離開迴圈 檢查回傳值,有錯誤跳到函數結尾 另外還有一個好處是for迴圈做的事情可抽象成子函數 goto滿好用,但濫用會多此一舉 下面的例子是函數剛開始分配記憶體 int function(...) { int ret = ERR_NONE; type *ptr = NULL; ptr = (type *)malloc(sizeof(type)); if (!ptr) { ret = errno; report_error_log(ret); goto _exit; } ... _exit: free(ptr); return ret; } 上述的goto是多做的,記憶體沒有成功分配,ptr仍是NULL,不用釋放 所以直接return就好 ptr = (type *)malloc(sizeof(type)); if (!ptr) { ret = errno; report_error_log(ret); return ret; } 所以goto應該不是全都不好,事在人為啦 僅供參考 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 218.161.19.142 ※ 文章網址: https://www.ptt.cc/bbs/Gossiping/M.1478362070.A.FED.html

11/06 00:09, , 1F
教我寫程式!!!!!
11/06 00:09, 1F

11/06 00:09, , 2F
先推不然被說文組
11/06 00:09, 2F

11/06 00:10, , 3F
嗯嗯 沒錯 就是我想的那樣
11/06 00:10, 3F

11/06 00:10, , 4F
還好c# 直接dispose就好 using 還可以自動釋放
11/06 00:10, 4F
自動釋放真的方便許多

11/06 00:13, , 5F
自動釋放才靠北,新手容易寫到有 memory leak
11/06 00:13, 5F
能把自動釋放運用到漏記憶體也是很神XD

11/06 00:15, , 6F
通常用完文件就可以弄掉了呀
11/06 00:15, 6F

11/06 00:16, , 7F
最基礎的 static event,一堆人不知道要放掉
11/06 00:16, 7F
@@" C#的進階領域了...

11/06 00:20, , 8F
寫中文很難嗎??
11/06 00:20, 8F

11/06 00:22, , 9F
我怎覺得你這goto用法跟把free單獨拉出來寫成函式差不多
11/06 00:22, 9F
指標不多應該是不用 若指標一多我會先把某些邏輯包括他用到的指標包成子函數 作法就會跟例子一一樣了

11/06 00:29, , 10F
我很喜歡在巢狀迴圈中 滿足條件 不再執行時用goto 爽
11/06 00:29, 10F
嗯其實青菜蘿蔔問題,這點我會遵守結構化程式的原則 例如 int i = 0; for (; i < N; i++) { if (滿足某條件) { ... /* 成功跑完滿足條件的案例 */ break; } } 我會寫成 int i = 0; for (; i < N; i++) { if (不滿足某條件) continue; /* else */ ret = function_a(...); if (ret != ERR_NONE) break; ret = function_b(...); if (ret != ERR_NONE) break; ... /* 成功跑完滿足條件的案例 */ break; } if (ret != ERR_NONE) goto _exit;

11/06 00:31, , 11F
恩恩 很好
11/06 00:31, 11F

11/06 00:41, , 12F
雖然我也不反對用GOTO 不過大部分時候看到寫爛的居多...
11/06 00:41, 12F
用goto變成很多邏輯要反向思考真的要習慣 正常執行的邏輯變成隱含的else了 ret = function_a(...); if (ret != ERR_NONE) { report error goto _exit; } /* 隱含的else */ ret = function_b(...); if (ret != ERR_NONE) ...

11/06 00:45, , 13F
嗯 我goto的例子看最多的就是集中釋放記憶體的寫法
11/06 00:45, 13F
Linux driver裡面例子超多

11/06 00:49, , 14F
推 ~ 認真回
11/06 00:49, 14F

11/06 00:51, , 15F
要用這種寫法時,釋放時要檢查是否有分配過才動作
11/06 00:51, 15F
是指下述寫法? if (ptr) free(ptr); 以前編譯器寫得不好的時候直接free是有可能程式崩潰 不過今日的free函數給NULL不會有動作 google "free null pointer"第一篇 但如果要釋放的是結構而非型態 最好寫函數去將結構下分配的子結構釋放

11/06 00:53, , 16F
推 認真回
11/06 00:53, 16F

11/06 01:04, , 17F
11/06 01:04, 17F

11/06 01:06, , 18F
ぬるぽ
11/06 01:06, 18F

11/06 01:15, , 19F
好厲害啊
11/06 01:15, 19F

11/06 01:16, , 20F
11/06 01:16, 20F

11/06 01:35, , 21F
嫌麻煩的話,第一個例子用macro就夠了吧
11/06 01:35, 21F
假設function_a到function_x都回傳int那值得設計一個巨集 如果中間回傳型態都不一樣就不用,不然維護者還要拆解理解巨集 int function_a(type *ptr); /* 使用者自訂 */ bool function_b(type *ptr); /* 作業系統API,失敗行設errno */ int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr 已經指向一個成功分配的記憶體空間 */ ret = function_a(ptr); if (ret != ERR_NONE) { report_error_log(ret); goto _exit; } if (!function_b(ptr)) { ret = errno; report_error_log(ret); goto _exit; } ... _exit: free(ptr); return ret; }

11/06 01:46, , 22F
嗯嗯 跟我想得差不多
11/06 01:46, 22F
※ 編輯: kphuang (218.161.19.142), 11/06/2016 01:57:04

11/06 02:19, , 23F
11/06 02:19, 23F

11/06 08:30, , 24F
看起來可以都用if else取代 請問可以嗎?
11/06 08:30, 24F
巢狀不要太深可以的

11/06 08:31, , 25F
觀念正確,很多人只是念書時被教不能用goto,但卻沒真
11/06 08:31, 25F

11/06 08:32, , 26F
的思考為何不能用,或是不該用,更或著是亂用
11/06 08:32, 26F

11/06 08:33, , 27F
不推會被人家知道我看不懂,推推推
11/06 08:33, 27F

11/06 09:39, , 28F
讀通Knuth的TAOCP是不是就會變成神人~
11/06 09:39, 28F
這本好像很厲害,有機會再拜讀,謝謝大大分享

11/06 09:47, , 29F
你的例子很差. 集中釋放資源可已用 function 取代
11/06 09:47, 29F
大大若有推薦的函數可以集中釋放資源望請不吝分享

11/06 09:48, , 30F
我是贊成用好goto的. 用在解決深度巢狀. 但程式上出現多層
11/06 09:48, 30F

11/06 09:49, , 31F
迴圈本身就有效率不佳的可能. 因此goto使用機率極低
11/06 09:49, 31F

11/06 10:48, , 32F
不是所有程式設計師都會動態分配記憶體,和各種狀況的錯誤處理
11/06 10:48, 32F

11/06 10:48, , 33F
,你講得太進階了
11/06 10:48, 33F

11/06 10:50, , 34F
C#不用管記憶超方便的,可是c++還是很好玩,一個一
11/06 10:50, 34F

11/06 10:51, , 35F
個物件自己慢慢刻
11/06 10:51, 35F

11/06 14:30, , 36F
推個。集中釋放資源的做法公司的code一直都有在用
11/06 14:30, 36F
這種寫法應該是能減少leak的 ※ 編輯: kphuang (218.161.19.142), 11/06/2016 15:48:37 ※ 編輯: kphuang (218.161.19.142), 11/06/2016 16:02:25 ※ 編輯: kphuang (218.161.19.142), 11/06/2016 16:03:28 ※ 編輯: kphuang (223.137.180.103), 11/06/2016 21:35:34
文章代碼(AID): #1O7WFM_j (Gossiping)
討論串 (同標題文章)
文章代碼(AID): #1O7WFM_j (Gossiping)