Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Splint C 程式靜態分析工具入門教學

編輯:Linux 教學

前言

Splint(Secure Programming Lint)是經典 C 語言靜態分析工具,透過原始碼註釋(Annotations)擴展類型系統,能檢測空指標解引用、未初始化變數、型別不匹配、緩衝區溢位風險等編譯器難以捕捉的缺陷。雖然現代有 Clang Static Analyzer、Cppcheck、Coverity,但 Splint 的註釋系統對理解「契約式設計」極具教學價值。本文涵蓋安裝、核心註釋語法、三大類檢查實戰。

一、安裝與環境設定


# 下載源碼(官網 splint.org 已停維,使用鏡像)
wget https://github.com/splint/splint/archive/refs/tags/3.1.2.tar.gz -O splint-3.1.2.tar.gz
tar -zxvf splint-3.1.2.tar.gz
cd splint-3.1.2

./configure --prefix=/usr/local/splint
make && make install

# 環境變數(~/.bashrc)
export LARCH_PATH=/usr/local/splint/share/splint/lib
export LCLIMPORTDIR=/usr/local/splint/share/splint/import
export PATH=/usr/local/splint/bin:$PATH

source ~/.bashrc
splint -version   # 驗證

二、核心註釋語法速查

註釋位置語意範例
/*@null@*/參數/返回值允許為 NULLvoid foo(/*@null@*/ char *p);
/*@notnull@*/參數/返回值保證非 NULL/*@notnull@*/ char *bar();
/*@in@*/參數調用前必須已初始化void read(/*@in@*/ int *p);
/*@out@*/參數調用後保證初始化void write(/*@out@*/ int *p);
/*@inout@*/參數調用前初始化、調用後可能修改void swap(/*@inout@*/ int *a, /*@inout@*/ int *b);
/*@observer@*/參數僅讀取,不獲取所有權void print(/*@observer@*/ FILE *f);
/*@only@*/參數/返回值轉移所有權(呼叫者不再負責釋放)/*@only@*/ char *alloc();
/*@owned@*/參數呼叫者保留所有權void free(/*@owned@*/ char *p);
/*@temp@*/參數暫時借用,函數返回前釋放void use(/*@temp@*/ char *buf);
/*@dependent@*/結構成員生命週期依附宿主物件struct { /*@dependent@*/ char *name; }

三、三大類檢查實戰

1. 空指標解引用檢查


// null.c
char firstChar1(/*@null@*/ char *s) {
    return *s;  // 警告:可能解引用 NULL
}

char firstChar2(/*@null@*/ char *s) {
    if (s == NULL) return '\0';
    return *s;  // 無警告:已檢查
}

$ splint null.c
null.c:3:11: Dereference of possibly null pointer s: *s

2. 未初始化變數檢查


// usedef.c
extern void setVal(/*@out@*/ int *x);
extern int getVal(/*@in@*/ int *x);
extern int mysteryVal(int *x);

int dumbfunc(/*@out@*/ int *x, int i) {
    if (i > 3) return *x;          // 警告:x 未初始化就讀取
    else if (i > 1) return getVal(x); // 警告:傳遞未初始化 x 給 @in@ 參數
    else if (i == 0) return mysteryVal(x); // 警告
    else {
        setVal(x);                 // @out@ 保證初始化
        return *x;                 // OK
    }
}

$ splint usedef.c
usedef.c:7:19: Value *x used before definition
usedef.c:9:18: Passed storage x not completely defined
usedef.c:11:22: Passed storage x not completely defined

3. 型別與布爾邏輯檢查


// bool.c
int f(int i, char *s, bool b1, bool b2) {
    if (i = 3) return b1;      // 警告:條件為賦值而非比較
    if (!i || s) return i;     // 警告:! 運算元非布爾、|| 右運算元非布爾
    if (s) return 7;           // OK:指標作為布爾語境隱式轉換
    if (b1 == b2) return 3;    // 警告:bool 直接 == 比較不可靠,建議用 &&/||
    return 2;
}

$ splint bool.c
bool.c:4:5: Test expression for if is assignment expression: i = 3
bool.c:4:5: Test expression for if not boolean, type int: i = 3
bool.c:4:8: Return value type bool does not match declared type int: b1
bool.c:5:6: Operand of ! is non-boolean (int): !i
bool.c:5:11: Right operand of || is non-boolean (char *): !i || s
bool.c:7:5: Use of == with boolean variables (risks inconsistency)

四、常用抑制選項


# 抑制特定警告類別
splint -predassign  # 忽略條件賦值警告
splint -predboolint # 忽略條件非布爾警告
splint -boolops     # 忽略布爾運算元警告
splint -boolcompare # 忽略 bool == 比較警告
splint -type        # 忽略型別不匹配警告
splint -usedef      # 忽略未初始化警告
splint -compdef     # 忽略未完全定義警告

五、實務建議與現代替代

  • 漸進式導入:先跑 splint -weak file.c(僅標準檢查),再逐步加入註釋。
  • 關鍵模組優先:針對安全敏感、指標運算密集的模組(網路協定解析、檔案格式處理、驅動程式)投入註釋成本。
  • CI 整合:splint +weak -warnposix *.c 作為建置門檻。
  • 現代替代:
    • clang --analyze / scan-build:LLVM 原生、路徑敏感、支援 C++、零註釋即可用。
    • cppcheck --enable=all --std=c11:速度快、誤報低、支援 MISRA/CERT 規則集。
    • Coverity Scan / CodeQL:企業級、跨語言、資料流分析更深。

六、總結

Splint 以「註釋即契約」思想,強迫開發者在介面層面明確所有權、空值、初始化責任,這種嚴謹思維可移植至任何語言的 API 設計。雖工具本身已較少維護,但其核心概念仍是靜態分析與安全編碼的基石。

Copyright © Windows教程網 All Rights Reserved