謝邀。
在C語言程序開發中,調用一個有返回值的函數時,一般要對函數的返回值做判斷,以確定函數是否按照預期執行。如果被調用函數沒有按照預期執行,最好加上相應的錯誤處理代碼,否則最終編譯得到的C語言程序穩定性就不夠好,遇到一點點意外,可能就不會正常工作了。
沒有判斷C語言函數的返回值,會有什么問題?
例如下面這段C語言程序:
上面這段C語言程序首先定義了一個buf并且把它清零,程序員的本意是在something文件里存放一串字符串,并且通過read()函數將其讀出,然后打印到控制臺。
但是,可能因為某種原因,something文件沒有生成,那么上面這段C語言代碼編譯得到的程序就什么也不會輸出了。遇到這種什么都沒有輸出的情況,初學者甚至可能會以為程序沒有運行。
要是這段C語言代碼隱藏在一個比較大的項目間,something文件是由其他邏輯生成的,這時要定位問題代碼可能就要花些功夫了。
再看一個例子,相關C語言代碼如下:
可以看出,程序員的本意是申請一塊內存,并且讓buf指向它,然后在buf中存放一段字符串并輸出到控制臺。然而,這段代碼隱藏著一個比較嚴重的隱患——malloc()是有可能失敗的。如果malloc()函數執行失敗,buf會指向NULL,此時sprintf()函數就會操作空指針,引發段錯誤(Segmentationfault)。
C語言程序中的“段錯誤”出現時,通常不會有其他錯誤提示信息,這對于調試來說是比較難受的。不過在Linux中可以設置coredumped,利用gdb等工具排查。不管如何,“段錯誤”都是相對來說比較難定位的錯誤。
所以,在編寫C語言程序時,判斷函數的返回值非常重要。通過返回值,我們能夠知道函數有沒有正常運行,如果它沒有正常運行,就做相應的錯誤處理,只有這樣,最終得到的C語言程序才能應對各種意外。
加上函數返回值判斷
現在將上面兩段C語言代碼改寫——增加返回值判斷邏輯:
上面的C語言代碼在可能會出錯的地方做了判斷,并且編寫了額外的錯誤處理代碼。在本例中,如果malloc()執行失敗,就向控制臺打印“mallocfailed”以及錯誤原因,并且終止程序。open()函數和read()函數也做了類似的錯誤處理。
這里值得一提的一個小技巧是:LinuxC語言編程中,很多函數執行失敗時,系統會將錯誤碼放入errno。我們當然可以直接獲取errno,將其轉換成錯誤字符串也是可以的:
但是更方便的做法是借助printf()函數提供的“%m”,以下兩行C語言代碼是等價的:
現在編譯上面的C語言代碼并且執行,得到如下輸出:
這樣一來,就算C語言程序遇到“意外情況”,程序也不會反應都沒有。從這段輸出我們可以知道:open()函數執行失敗了,原因是“Nosuchfileordirectory(沒有這樣的文件或者目錄)”。對于本例而言,這是因為當前目錄沒有something文件。
去掉啰嗦的語句
請看下面的C語言代碼:
可以說這段代碼非常的“臃腫”和“啰嗦”,多個if-else堆疊在一起,讀起來比較難受。如果調用的函數再多一點,if-else堆疊再多一點,代碼的可讀性就更差了。所以,在實際C語言項目開發中,一般都不這么寫。
之前的文章介紹過使用do{}while(0)將若干代碼封裝成宏的技巧,其實do{}while(0)的作用還不止于此,請看下面的C語言代碼:
上面的C語言程序借助break語句,在遇到錯誤時,就直接跳出do{}while語句,可以看出,代碼簡潔多了。不過仔細想想,這種方法還是有不方便的地方。請再看看上面這段C語言代碼,應該能夠發現,close(fd);和free(buf);語句都需要寫多次。如果調用的函數再多一些,需要重復寫的語句就更多了。這很不友好,也很不利于維護。
那該怎么辦呢?
雖然很多人不提倡使用goto語句,但是它確實能夠很方便的處理錯誤,請看下面的C語言代碼:
將錯誤處理語句統一放在最后處理,整個代碼顯得非常簡潔,也非常易讀和維護。不過,這種寫法對變量的初值有一定的要求。
想想為什么?
小結
本節介紹了C語言程序開發過程中,判斷函數返回值的重要性。并且介紹了兩種處理錯誤邏輯的“技巧”,讀者可自行比較這幾種風格代碼,歡迎在評論區分享您的觀點。
歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。