許多 C 教材(含經典譚浩強教材)對「函數定義」「函數聲明」「函數原型」三者關係描述不精確,甚至錯誤。本文結合 C 語言歷史演進(K&R C → C89/C90 → C99),從編譯器實作角度徹底釐清三者概念、語法形式與檢查機制。
| 術語 | 定義 | 關鍵特徵 |
|---|---|---|
| 函數定義 | 包含函數體的完整實作 | 同時也是一種聲明;分配代碼空間 |
| 函數聲明 | 向編譯器報告函數存在及介面 | 包含:舊式聲明、函數原型、函數定義 |
| 函數原型 | 參數型別列表的聲明 | double foo(int, char); 或 double foo(int a, char b) { ... } |
/*** 古老寫法 ***/
main() { printf("hello\n"); } /* 隱式 int 回傳 */
double sqrt(); /* 僅聲明回傳型別,無參數資訊 */
main(argc, argv) int argc; char *argv[]; /* K&R 參數宣告風格 */
intdouble sqrt(); 僅告知回傳型別,參數完全不檢查/*** 原型風格 ***/
double square(double x); /* 參數型別列表 */
double square(double); /* 參數名可省略 */
int main(void) { ... } /* 建議寫 void */
double square(double x) { return x*x; } /* 定義亦為原型 */
() 空參數列表標記為「過時」,但兼容保留int 仍可省略(C90 容忍)int main(void):不得省略回傳型別int 回傳被廢除錯誤。定義 是 聲明的一種(子集)。如同「男人是人」,不能說「男人與人不是一回事」。正確說法:「無函數體的聲明(函數類型聲明)與定義不是一回事」。
不完整。舊式聲明 double sqrt(); 既無函數體,也不檢查參數。只有函數原型才檢查參數。函數定義包含函數體且也檢查參數。
錯誤。函數型別包含:回傳型別 + 參數型別列表(若為原型)。兩個函數回傳型別相同但參數不同,型別不同。
/*** header.h ***/
#ifndef HEADER_H
#define HEADER_H
/* 僅原型聲明,無函數體 */
double square(double x);
void process_data(const int *arr, size_t n);
#endif
/*** source.c ***/
#include "header.h"
/* 定義即實作,同時滿足原型要求 */
double square(double x) { return x * x; }
void process_data(const int *arr, size_t n) {
for (size_t i = 0; i < n; ++i) { /* ... */ }
}
| 聲明形式 | 回傳型別檢查 | 參數個數檢查 | 參數型別檢查 | 備註 |
|---|---|---|---|---|
| 無聲明 (C89 前) | 假設 int | 無 | 無 | 極危險 |
舊式 double foo(); | ✅ | ❌ | ❌ | C99 廢除隱式調用 |
原型 double foo(int); | ✅ | ✅ | ✅ | 標準做法 |
定義 double foo(int x){} | ✅ | ✅ | ✅ | 同時為聲明 |
static 內部函數定義在調用前或檔案頂部前向聲明-Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition 強制規範