學校裡學不到的C語言教程之3:學習異常處理然後忘掉它

2020-12-14 clq的程式設計師學前班

我們本系列的上篇文章說了自己分配內存的重要性,因為那很容易引進程序的崩潰。在原始的 C/C++ 中,這是經常發生的一件事情。除此之外引起崩潰的原因非常多,常見的有:訪問指針異常、除0錯、訪問 NULL 指針、文件訪問錯誤以及作業系統異常等等。對於一個剛出校門走上工作崗位的 C 語言程式設計師來說,在默認的情況下 C 語言程序只要遇到以上情況就一定會崩潰這種事實是非常打擊人的。如果你用的是 vc、bcb 這樣著名的 ide 還是好的,它們會默認處理一些常見錯誤。如果這位初學者的崗位是在 linux 下的,那基本上就是災難了:他一定會經常碰到 linux 下最著名的問題 -- 段錯誤。段錯誤的表現為,寫得好好的程序總會在某個時刻彈出這個信息然後就退出了。然後你就找啊找,到底哪裡出了問題。你會在網上搜索各個解決辦法,估計大部分找傳過來後都會在各個可能出錯的地方打上 printf。如果你的程序是單線程的,那倒也罷了,printf 會很有效的提示出錯誤的位置。如果你的程序是多線程的,而且還是伺服器 ... 我個人覺得,除非您所在的公司很有錢,要不這一定是個坑,幾年內是出不來的,工作之餘多存點錢以便被炒的時候 ... 真的,一個剛工作的初學者能維護好一個 C/C++ 伺服器程序的可能性無限接近於0!但在我接觸過使用 delphi 的公司中,居然有很多伺服器是初學者寫的,雖然其中有不少錯誤,但是他們寫的程序是不會動不動就崩潰的。同樣的道理也適用於 java 和 C#。有一定工作經驗的同學一定會同意我的這個觀點。

那麼這種情況是怎樣造成的呢?原因就在於默認的情況下,C語言的錯誤處理就是直接退出!說實在的我覺得這真是一個腦殘的設計思想,因為年代久遠已經無法追溯這是怎樣形成的了。但現狀就是如此,我們只能自己想辦法去處理和適應。我們先來看看其他語言是怎麼處理的,就先以受眾最多的 java 為例吧。學過 java 的同學應該都學過它的歷史都知道 java 本來就是為了解決 C 語言的很多問題而產生的,既然錯誤的處理方式很爛,那 java 當然是要重點處理的了。java 的處理方式就是加上異常處理,當出錯時說清楚怎樣做就可以了,不需要退出(具體代碼我們就貼了),而且還要求在所有可能出錯的地方都要寫上異常處理,就是那個著名的 try 代碼塊。不過我個人覺得 java 的處理方式太深究化,實際工作中太麻煩,我個人覺得最好的方式是 delphi 的,它象 java 一樣可能以顯示存在,不存在的情況下則會默認只跳出一層,而不是整個程序(估計初學者也看不懂什麼是跳出一層,這個一時也解釋不清楚)。因為 try 的代碼塊處理方式非常有效,所以現在的語言大多這樣處理。當然 golang 是例外的,因為 try 的概念已經根深蒂固,所以我花了近兩周才學會 golang 的異常處理,總的來說 golang 的處理介於 java 和 delphi 之間,印象中它是唯一不使用 try 代碼塊的異常處理語言。

好了,既然業界都有處理方法了,C/C++ 裡不可能沒有這個 try 吧。先說好消息,是的,有的。等等,先別高興得太早 C++ 中的異常處理基本上只針對類的異常,對於系統的異常是一樣崩潰的。而 C 語言裡呢,各個廠商也擴展了 try 語言塊,它的調用方式大概是這樣:

try{各種操作 } catch(...) { 錯誤處理 }這裡又能正常運行了

你一定以為萬事大吉了。如果您用的是 bcb,那差不多吧。如果是 vc 情況很複雜,我們先說 gcc 的情況吧:gcc 下完全沒有用,而且默認下根本不支持 try。得了,我們都用 vc ... 好,我們來看下 vc 的情況,測試代碼如下圖:

異常測試代碼

源碼如下(因為可能會被過濾某些符號,所以請參考上面的圖片):

int _tmain(int argc, _TCHAR* argv[]){ //char * s = "1234567890"; char * s = NULL; //char s[] = "1234567890"; //換成這個數組聲明的方式內存就不會報錯了 //char * s = (char *)malloc(11); //C++ 裡還要加強制轉換 try{ memset(s, 0, 11); //一定要清空 strcpy(s, "abc"); //printf("Hello World!\r\n"); printf("Hello World!%s\r\n", s); //free(s); } catch(...){} printf("正常退出\r\n"); //C中的 try 是無用的,到不了這裡 return 0;}

如果是 bcb 那麼這段代碼會輸出 "正常退出" 的。如果是 java 或者 delphi C# 等現代語言也會,但 VC 的情況很複雜,以下是其 2010 版本的發布模式的編譯運行結果:

VC下沒有起作用

可以看到,這裡的 try 根本沒起使用。不過 VC 下另有 __try 語言塊支持,並且還有 debug 模式,在各種選項的組合下 VC 是可以支持的,不過時間關係我們就不展示了。總之 VC 是可以做到的,不過不是用默認的 C/C++ 異常處理方式。

bcb正常

正因為以上的 C/C++ 異常處理的複雜性,特別是 gcc 下沒有好的處理方法(暫時)。所以現在業界的做法大多其實是不做異常處理!是的,您沒看錯,至少是沒有做太多的異常處理,開源界就更是如此了,開源界基本上不處理或者是用長跳轉的方式(具體我們就不介紹了)。還有一種常見的就是加退出處理函數,在程序就要崩潰退出前顯示一個對話框,例如常見的迅雷的錯誤退出對話框和 firefox 的錯誤對話框想必大家都見過吧。你會說這麼恐怖!那麼伺服器程序怎麼辦?是這樣的,通常伺服器會弄成兩個程序,一個是主業務,跑邏輯,另外一個專門監控這個主業務程序,如果前面的那個崩潰了,那麼監控的這個會把它再啟動 ... 雖然不可思議,但事實就是如此,比如大家常用的 php就是這樣的。另外一些單機程序也是,比如早期的 google 瀏覽器的部分功能也是獨立的 exe 在後臺跑的。遺憾的是,很多國內的公司並不知道這樣做。

要說的是,雖然所說 gcc 不久會有"真正的"異常支持,VC也可以實現 java、C# 那種級別的異常支持。但是異常不是萬能的,有一些錯誤是無法用異常跳過的,正確的方式還是寫好程序,考慮好可能發生的錯誤情況。比如我從業生涯中就見過好多次整個 java 程序都崩潰的情況,delphi 也是;golang 中的 cgo 異常也是無法完全處理的(所以我現在基本不用 cgo,也希望大家發行量不要用)。有個真實的故事:過去手機程序 kjava 流行時有一個同時面對公司 kjava 程序莫名其妙的運行結果時抱怨還不如象 C 語言一樣直接退出呢。所以異常這個東西也是雙刃劍,既然現在 C 對它的支持還不完善那還不如不要用它!

最後我想說,我個人覺得使用監控程序的方式目前來說是最好的解決方案,當然這可能和我主要從事伺服器端的開發有關係。

相關焦點

  • 學校裡學不到的C語言教程之6:可怕的浮點數
    但它其實上不是,其實上它是另外一種情形:是我一入行就有前輩很鄭重告知的一個問題。說到這位前輩,我忍不住要給正準備走出校園的那些同學們上一個不太愉快的課。當我們離開學校走入社會時,就要忘掉那些時刻都能幫助我們的學長、同學、甚至老師們。一般情況下你們很少能碰到我這樣熱心新人的老油條,絕大多數下我們能依賴的只有我們自己 ...
  • 學校裡學不到的C語言教程之4:奇怪的第一行代碼與重複的函數名
    當我們學習 C 語言一定時間後,一定會很熟悉在編寫各種功能代碼邏輯之前一開始就會要 include 一堆的 h 頭文件。例如我們大家都知道的 "#include <stdio.h>"。漸漸的我們會知道,有些功能是要再引入其他的庫才能使用的。
  • C語言的那些小秘密之異常處理
    第一行代碼定義了一個函數指針(註:如果有對函數指針知識點不熟悉的讀者可以去閱讀我之前寫的那篇文章《C語言的那些小秘密之函數指針》),其類型為含有一個int型參數,無返回值;  第二行代碼中,signal函數的返回值是一個函數指針,與第一行我們定義的類型相同,第二個參數也為一個函數指針,其實signal的返回值就是第二個函數指針指向的函數地址。
  • C語言入門教程-Scanf
    打開APP C語言入門教程-Scanf 佚名 發表於 2009-07-29 10:44:04 原因是它不能很好地處理人為錯誤。不過,對於簡單的程序來說,scanf還是很合適的,而且易於使用。 scanf的最簡單用法是像這樣: scanf("%d", &b); 該程序將讀取用戶從鍵盤輸入的一個整數(和在printf中一樣,%d代表整型,因此b必須聲明為int),並將其存入b。
  • C語言簡明教程(一)C語言簡單剖析
    他說「大學生畢業前要學好 C 語言,C 語言是當前程式設計師共同的語言,比你在大學學到的現代語言(比如 ML,java,python 或者其它流行的語言)都更接近機器」。他指出「不管你懂多少延續、閉包、異常處理,只要你不能解釋為什麼 while(*s++=*t++) 的作用是複製字符串,那你就是在盲目無知的情況下編程,就像一個醫生不懂最基本的解剖學就在開處方」。
  • c語言入門教程
    它的應用範圍廣泛,具備很強的數據處理能力,不僅僅是在軟體開發上,而且各類科研都需要用到C語言,適於編寫系統軟體,三維,二維圖形和動畫,具體應用比如單片機以及嵌入式系統開發。1994年,ISO修訂了C語言的標準。  1999年,ISO又對C語言標準進行修訂,在基本保留原來C語言特徵的基礎上,針對應該的需要,增加了一些功能,命名為ISO/IEC9899:1999。  在ANSI標準化後,C語言的標準在一段相當的時間內都保持不變,儘管C繼續在改進。它被ANSI於2000年3月採用。  2001年和2004年先後進行了兩次技術修正。
  • 單片機C語言教程-基礎語句
    C語言入門之基礎語句  從程序流程的角度來看,程序可以分為三種基本結構,即順序結構、分支結構、循環結構。c語言提供了多種語句來實現這些程序結構。本文將介紹這些基本語句及其應用,使讀者對c程序有一個初步的認識,為以後的學習打下基矗  c程序的語句  c程序的執行部分是由語句組成的。程序的功能也是由執行語句實現的。
  • 適合具備 C 語言基礎的 C++ 教程(十二)
    引言在前面的教程中,敘述了模板函數以及模板類的相關概念,在本節教程中,筆者將著重敘述 C++中的異常機制,所謂異常,是程序在執行期間產生的問題,
  • C語言簡明教程(九)指針(二)
    在 C 語言中只有字符變量,沒有字符串變量。以下語句 printf("%s\n",string);中 %s 是輸出字符串時所用的格式,在輸出項中給出字符指針變量名 string,則系統會輸出 string 所指向的字符串第一個字符,然後自動使 string 加 1,使之指向下一個字符,再輸出該字符...如此直到遇到字符串結束標誌 '\0',因此再輸出是能確定輸出的字符到何時結束。
  • 推薦零基礎C語言教程【一】
    贊助群目前資料火熱更新中,最近也是找人搞了點進階安全課程給大家,希望贊助群的各位能好好學習
  • C語言代碼中異常的處理機制
    C語言代碼中異常的處理機制   C++的異常機制為我們提供了更好的解決方法。異常處理的基本思想是:當出現錯誤時拋出一個異常,希望它的調用者能捕獲並處理這個異常。如果調用者也不能處理這個異常,那麼異常會傳遞給上級調用,直到被捕獲處理為止。如果程序始終沒有處理這個異常,最終它會被傳到C++運行環境,運行環境捕獲後通常只是簡單地終止這個程序。
  • C語言簡明教程(四)選擇程序設計
    C語言簡明教程
  • C語言入門教程(一)
    C語言入門教程(一):輸入輸出函數、程序中的數據實驗環境Ubuntu 16.04 終端gcc
  • Python「守護者」之異常處理(下)
    大家好,今天我們再繼續聊聊Python「守護者」之異常處理,首先舉一些例子,讓大家再熟悉熟悉異常處理的其他家庭成員。1)NameError上面的英文是什麼意思呢?NameError:當一個局部或全局的變量不能被找到時促發;與該異常相關聯的值是一個錯誤信息,其中包含未被找到的變量名,該錯誤信息也就是我們在上一篇文章中使用的異常類型 as變量名中變量所對應的值。不理解不要緊,我們看一下示例:try語塊中有賦值語句b = c,但是c並未被定義過,這肯定是有問題的。分析完畢,我們執行一下代碼,看看運行效果。
  • 學校裡學不到的C語言教程之10:一定要會的移位和16進位轉換原理
    它的實際用途可以用常見的字節內容顯示為16進位字符串的算法來做個示例。在學校時,我們就學過可以用 printf 來輸出一個數據的 16 進位內容,例如:/*按16進位輸出,默認右對齊*/printf("%x\n",PrintVal);在 delphi 等於開發語言中也有類似的函數,但象"quoted-printable
  • 學習c語言,知道這三個資源就行了
    c語言作為目前最熱門的程式語言之一,一直是學生、程式設計師必須學習的課程之一,但目前許多學校的課程太過老舊,教材和教程也參差不齊,這裡就給大家推薦幾個比較好的學習c語言的資源。第一個推薦的就是《C Primer Plus》,作為一本廣受大家認可的工具書,書中詳細地講解了C語言的基本概念和編程技巧,並且包含了許多習題和例子,可以有效的幫助初學者建立對於C語言的知識體系,是一本對小白比較友好,且內容全面的入門教材。
  • R語言入門教程 | tidyverse包之數據處理
    大家在學習R語言的時候,大多參考《R語言實戰》這本書,但這本書年代過於久遠
  • 像黑客一樣學英語:學校裡學不到的那些實用主義「套路」
    在其中一項課程《學習如何學習》的選修課件裡,主講教授採訪了來自愛爾蘭的」語言黑客」 Benny Lewis —— 一個通過跨國旅行,不斷成功用三個月時間掌握新語言的「文瘋子」。▲ Benny Lewis在長城的「到此一遊照」。他通過遊學的方式,嘗試在3個月內掌握當地語言,現在他能熟練使用10種以上的語言。
  • 教程|R語言學習與實踐教程
    我創建了R語言微信群,定位:R語言學習與實踐,要進群的朋友,添加我微信:luqin360。本文的外部連結,請點擊閱讀原文進入到數據人網後,就可以訪問和查看了。在這個內核中,我收集了我所寫的所有R語言教程,按級別劃分。初學者:在這個級別,我假設你沒有編程背景或剛剛開始使用R.
  • c語言程序設計自學教程
    c語言十分依賴於計算機思維,而思維的培養不是一日之功,而是一個日積月累的過程一:準確把握語法語句概念1、編譯預處理不是C語言的一部分,不佔運行時間,不要加分號。C語言編譯的程序稱為源程序它以ASCII數值存放在文本文件中。2、#define PI 3.1415926; 這個寫法是錯誤的,一定不能出現分號。 3、每個C語言程序中main函數是有且只有一個4、在函數中不可以再定義函數。