在前幾節中,我們探討了透過函數參數(傳值呼叫)與指標(傳址呼叫)來實現主呼叫函數與被呼叫函數之間的資料交換。雖然這兩種機制是標準做法,但在某些情境下——例如參數眾多、層級較深的呼叫鏈——逐層傳遞參數會顯得繁瑣且易出錯。此時,將需共享的變數宣告為全域變數,便能讓它在整個程式的所有函數中「可見」,相當於隱性地完成了參數的傳遞與回傳。這在早期的 C 語言教學與實務專案中頗為常見。
全域變數的優點顯而易見:免去參數列表的撰寫與維護,程式碼看似簡潔。然而,代價是它們會長期佔用靜態記憶體空間,直到程式結束才會釋放。對於大型陣列或結構體,這可能導致記憶體浪費,甚至超出小型嵌入式系統的限制。好在現代桌面與伺服器環境記憶體充裕,多數教學範例與中小型專案仍能輕鬆負擔。
以下程式示範如何在 main 與 disp 之間,透過全域陣列 m 直接交換資料:
#include <stdio.h>
#include <conio.h> /* getch() 所需 */
void disp(void); /* 函數原型宣告 */
int m[10]; /* 全域變數:整個程式可見 */
int main(void)
{
int i;
printf("In main before calling\n");
for (i = 0; i < 10; i++) {
m[i] = i; /* 初始化陣列 */
printf("%3d", m[i]); /* 輸出呼叫前的值 */
}
disp(); /* 呼叫子函數 */
printf("\nIn main after calling\n");
for (i = 0; i < 10; i++)
printf("%3d", m[i]); /* 輸出呼叫後的值 */
getch();
return 0;
}
void disp(void)
{
int j;
printf("\nIn subfunc after calling\n"); /* 子函數內輸出 */
for (j = 0; j < 10; j++) {
m[j] = m[j] * 10; /* 直接修改全域陣列 */
printf("%3d", m[j]);
}
}
int m[10]; 置於所有函數之外,故屬外部連結,全程可見。disp() 參數列為空,仍能直接讀寫 m。disp() 後,main 中的 m 已被乘以 10,體現「隱性回傳」。disp 迴圈條件誤寫 i<10,此處已修正為 j<10,並同步修正列印索引。g_(如 g_config)以利辨識。get_g_config() / set_g_config() 函數,集中管理鎖機制、邊界檢查與日誌。static 檔案作用域變數,避免汙染全域命名空間。總結:全域變數是把「雙面刃」,善用可簡化架構,濫用則釀成維護惡夢。現代 C 開發傾向「最小權限原則」——優先以參數傳遞、結構體封裝、模組化介面取代全域狀態,僅在極少數經評估必要的場合引入全域變數。