《C 程式設計》經典教材中,常以 static 局部變數實作階乘函數作為範例。乍看輸出正確,實則埋下嚴重設計缺陷。本文深度剖析此反模式,揭示函數應具備的「無狀態、低耦合」本質。
int fac(int n) {
static int f = 1; // 靜態局部變數
f = f * n;
return f;
}
int main() {
for (int i = 1; i <= 5; ++i)
printf("%d!=%d\n", i, fac(i)); // 輸出 1,2,6,24,120
// 若再呼叫 fac(3) → 返回 360 (錯誤!)
}
將 fac() 內聯展開至 main() 可見端倪:
for (int i = 1; i <= 5; ++i) {
static int f = 1; // 等價於函數內的 static
f *= i;
printf("%d!=%d\n", i, f);
}
此時 i、f、f*=i 三者形成有機整體。原書將其「閹割」拆分:static f 被囚禁於函數內部,導致函數行為強依賴呼叫次序與呼叫歷史。
n=3)產生不同輸出,函數不再是純映射// 遞迴版
int fac_recursive(int n) {
return (n <= 1) ? 1 : n * fac_recursive(n - 1);
}
// 迭代版(推薦)
int fac_iterative(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) result *= i;
return result;
}
此版本:無隱性狀態、可重入、可並行、易測試、符合數學定義。
並非完全禁用,適用場景極窄:
// 正確範例:月份名稱查找表
const char* month_name(unsigned m) {
static const char* const names[12] = {
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
};
return (m >= 1 && m <= 12) ? names[m-1] : NULL;
}
此處 static 僅為避免重複建構表格,不涉及狀態變更,完全符合低耦合原則。
函數應像數學函數:同輸入必同輸出,無副作用,無歷史包袱。濫用 static 局部變數製造隱性狀態,是將完整邏輯「閹割」成殘缺組件的變態行為。寫出可維護、可測試、可並行的代碼,請堅守無狀態設計。