C 語言程式設計教學:檢查 C 程式

本文列出一些檢查 C 程式碼的要點,分為三個部分:

  • 撰碼風格
  • 靜態程式碼檢查
  • 記憶體使用檢查

撰碼風格 (Coding Style)

撰碼風格 (coding style) 或撰碼標準 (coding standard) 是指在撰寫程式碼時排比程式碼的方式。對於中大型專案,保持一致的撰碼風格,閱讀程式碼時比較賞心悅目,也比較容易理解一些。有些程式撰寫者憑著直覺去寫程式碼,不太注重撰碼風格;有些程式撰寫者對撰碼風格有著宗教般的情懷。由於撰碼風格並不是死記一套規則後馬上就會寫,而是在學習的過程中逐漸養成自己的習慣,所以我們在一開始時就提出這個概念。

常見的 C 撰碼風格有以下數種:

註:K & R 風格是指出現在 The C Programming Language 一書中的撰碼風格,但原著中並沒有明列該風格的規則。

如果在科技公司上班的話,公司內部可能也會有自己的撰碼風格,撰寫公司專案時請以公司內部的風格為標準;撰碼風格的出發點是在同一份專案內保持一致,不必變成口水戰的話題。

TAB vs. Space

建議:不要調整 TAB 相對應的空白數,讓 TAB 保持 8 個空白的寛度,除此之外,使用空白鍵 (space) 來排版。

由於 C 語言沒有限制空白的使用方式,程式撰寫者可以依自己喜好來整理 C 程式碼。一般常見的縮進 (indentation) 方式有 2 個空白、4 個空白、8 個空白或一個 TAB;筆者目前仿 K & R 風格,用 4 個空白,讀者可自己選用自己喜好的方式。TAB vs. space 也是早期的口水戰來源之一,但現在有許多自動重排程式碼的軟體,戰這個其實義意不大。

TAB vs. space 真正會出問題的地方在於撰寫 Makefile 時。由於 Makefile 一定要用 TAB 來縮進,但 TAB 和空白鍵在視覺上無法區分,所以一般會建議在 C 程式碼中用 2 個空白或 4 個空白,而保留 TAB 為相當於 8 個空白的寛度,並僅用於 Makefile 中。

自動程式碼重排

雖然保持良好的撰碼風格對專案有所幫助,但手動修正大量程式碼相當耗時、沒效率,透過軟體可以減輕一部分的工作。有些 IDE 內建自動重排的功能,可以省下手動排版的時間。或者可以用一些終端機工具,如下:

由於 C 語言的撰碼風格不只一種,這類程式碼重排工具有許多細微的選項可調整。如果願意花一些時間,可以自行調整出一套風格設定檔,以後就持續沿用下去。對於初學者來說,也有一些「套餐」可以選擇;像是 indent 指令支援 -gnu (GNU)、-kr (K & R)、-orig (BSD) 等參數。台灣不少 C 程式書藉採用接近 K & R 的風格,初學者可以參考。

以下 indent 指令可快速以 K & R 風格排列程式碼,將原有程式碼以新的風格取代:

$ indent -kr file.c

也可以用 astyle 達到同樣的效果:

$ astyle --style=kr file.c

靜態程式分析 (Static Code Analysis)

除了修正撰碼風格外,還可用靜態程式分析工具 (linter) 掃描程式碼以檢查一些不佳的寫法,之後再手動修改。一些工具如下:

以下是一個簡短的不良例子:

#include <stdio.h>

int main()
{
    if (1) {
        printf("true\n");
    } else {
        printf("false\n");
    }

    return 0;
}

splint 檢查此程式碼:

$ splint file.c
Splint 3.1.2 --- 21 Sep 2017

file.c: (in function main)
file.c:5:9: Test expression for if not boolean, type int: 1
  Test expression type is not boolean or int. (Use -predboolint to inhibit
  warning)

Finished checking --- 1 code warning

Linter 可以協助我們檢查出不是錯誤但相對不良的寫法,對於改良程式碼品質會有所幫助。

記憶體使用檢查 (Memory Usage Detection)

由於 C (或 C++) 需手動管理記憶體,即使程式執行正確,也有可能會出現記憶體洩漏 (memory leak)。程式開始用到 mallocfree 等相關函式時最好就加入記憶體使用檢查。對於初期寫的小型程式來說,即使忘了釋放記憶體,在程式結束後作業系統仍會回收記憶體,但最好還是在初期就養成良好的習慣。

在 GNU/Linux 上最知名的記憶體檢查軟體是 Valgrind,使用方式相當簡單:

$ valgrind ./file
==188371== Memcheck, a memory error detector
==188371== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==188371== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==188371== Command: ./file
==188371== 
==188371== 
==188371== HEAP SUMMARY:
==188371==     in use at exit: 0 bytes in 0 blocks
==188371==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==188371== 
==188371== All heap blocks were freed -- no leaks are possible
==188371== 
==188371== For counts of detected and suppressed errors, rerun with: -v
==188371== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

這個例子沒有記憶體洩露。我們來看一個有記憶體洩露的例子:

$ valgrind ./file
==188401== Memcheck, a memory error detector
==188401== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==188401== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==188401== Command: ./file
==188401== 
==188401== 
==188401== HEAP SUMMARY:
==188401==     in use at exit: 0 bytes in 1 blocks
==188401==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==188401== 
==188401== LEAK SUMMARY:
==188401==    definitely lost: 0 bytes in 1 blocks
==188401==    indirectly lost: 0 bytes in 0 blocks
==188401==      possibly lost: 0 bytes in 0 blocks
==188401==    still reachable: 0 bytes in 0 blocks
==188401==         suppressed: 0 bytes in 0 blocks
==188401== Rerun with --leak-check=full to see details of leaked memory
==188401== 
==188401== For counts of detected and suppressed errors, rerun with: -v
==188401== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Valgrind 是檢查整個程式,找出那個部分有記憶體洩露是程式設計者的責任。

目前 Valgrind 可在 GNU/Linux、Solaris、OS X (10.12 以前) 上執行,若讀者使用其他系統則需自行尋找替代方案。

C 本身沒有內建的垃圾回收器 (garbage collector),標準函式庫也沒有,如果需要時倒是有一些第三方方案,像是這個專案。學習 C 語言時,操作指標和管理記憶體也是學習的一環,最好不要一開始時就太依賴垃圾回收器。

comments powered by Disqus