這篇文章的主題是 static 這個關鍵字,而 static 可以搭配變數或函數使用。
首先來看把 static 用在變數時,共有以下兩種情境:
若一靜態區域變數寫在函數範圍內,代表這個變數「只初始化一次」,且生命週期是整段程式的執行期間、而非跟著函數共進退。
一般的區域變數是隨著函數呼叫被放在堆疊(stack)中,一旦函數執行完畢、離開堆疊,當中存放區域變數的空間也跟著被釋出。
區域變數的範例如下:
#include <stdio.h>
void foo() {
int count = 0; // 沒有設成靜態變數
count++;
printf("count = %d\n", count);
}
int main() {
foo(); // 首次呼叫,輸出 count = 1
foo(); // 再次呼叫,count 依然從 0 開始跑,輸出 count = 1
return 0;
}
foo() 函數被呼叫了兩次,但在第一次呼叫中,count 經過 count++ 變成 1 後,該函數被移出堆疊、這個值也不被保留,待第二次呼叫、重新把 foo() 放回堆疊時,count 被重新初始化,還是從 0 開始做 count++,顯示結果依然為 count = 1。
但如果加上 static 如下:
#include <stdio.h>
void foo() {
static int count = 0; // 只初始化一次,值會保留
count++;
printf("count = %d\n", count);
}
int main() {
foo(); // 第一次呼叫,輸出 count = 1
foo(); // 第二次呼叫,輸出 count = 2
return 0;
}
這時 count 變數是放在「資料區段(data segment)」,只要程式還沒運行完就不會消失,因此第二次呼叫 foo() 時,剛剛計算的 count 結果 1 會被保留,因此 count++ 是以 1 為基礎往上加,變成 2。
上述範例是將 static 寫在函數內的情況,但 static 也可以寫在函數外,變成全域變數。
全域變數不管是否有寫 static 都是放在資料區段,因此寫上 static 的效果為讓該變數僅在宣告時所在的檔案有效,無法被其他檔案使用,即「內部鏈接(internal linkage)」。
舉例來說,如果我們這樣定義全域變數:
// file1.c
#include <stdio.h>
int globalVar = 5; // 在 file1.c 定義的全域變數
int main() {
// ...
return 0;
}
// file2.c
#include <stdio.h>
extern int globalVar; // 在 file2.c 也可以使用 file1.c 定義的全域變數
int main() {
// ...
return 0;
}
這個 globalVar 變數除了可以在 file1.c 檔案中使用,也可以透過 extern 給 file2.c 或其他更多檔案使用。
但如果在 file1.c 的 globalVar 前面加上 static 如下:
// file1.c
#include <stdio.h>
static int globalVar = 5; // 在 file1.c 定義的全域變數
int main() {
// ...
return 0;
}
那就算 file2.c 有寫 extern int globalVar;,依然無法取用 globalVar 變數,看起來不太方便,但這其實讓我們得以在不同檔案都使用相同名稱的全域變數也不會互相干擾。
值得一提的是,如果沒有特別指定,則透過 static 宣告的變數預設值皆為 0,或者如果是指標變數的話,預設為指向 NULL。
當 static 被用在函數,其效果跟用在全域變數時相當,都是限制該函數只能在函數被宣告的檔案使用。
例如原本為:
// file1.c
#include <stdio.h>
void hello() { // 在 file.c 定義 hello() 函數
printf("Hello from file1!\n");
}
int main() {
hello();
return 0;
}
// file2.c
extern void hello(); // 取用 file1.c 的 hello 函數
int main() {
hello(); // 在 file2.c 也可以呼叫 file1.c 的 hello() 函數
return 0;
}
上述範例的 hello() 函數可以跨檔案使用,但如果在 hello() 前加上 static,如下:
// file1.c
#include <stdio.h>
static void hello() { // hello() 函數加上 static
printf("Hello from file1!\n");
}
int main() {
hello();
return 0;
}
// file2.c
extern void hello();
int main() {
hello(); // 這裡找不到 hello()
return 0;
}
這裡的 file2.c 就不能使用定義在 file1.c 宣告的 hello() 函數。
static 關鍵字可以搭配第四天提到的 inline 關鍵字,讓兩者各自發揮其效果,例如:
static inline int add(int a, int b) {
return a + b;
}
當中的 static 關鍵字代表此函數僅在函數被宣告的檔案可用,而 inline 代表此函數會直接在呼叫處展開,而不會有參數傳遞、出現新的堆疊框(stack frame)等呼叫開銷。
只要出現 static 關鍵字: